Jump to content
  • Advertisement
Sign in to follow this  
Vincent_M

Constructors, Factory Methods and Destructor Questions

This topic is 1273 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Is it always necessary to provide the default constructor (zero arguments) for your classes? I've been making it a point to provide a default constructor to my classes for the last couple years. There are many classes that are responsible for loading files, and I typically provide a constructor with a filename and path string for doing that. Then, the constructor becomes the loader. I've heard this is bad practice, and factory methods should be provided instead. I agree with using a factory method, but then how do I get around inheritance? What if I wanted to extend my object? I wouldn't be able to extend my factory function, or as I prefer, static factory method, if I wanted to add additional functionality to the subclass without providing a wrapper static method that eventually calls the superclass' static factory method.

 

Then, there are destructors... Is it good practice to always have virtual destructors? I always do this in case I want to extend that class. Since C++ doesn't allow us to declare a class as "sealed" or "final", I'd just always declare them as virtual so that they're always called.

 

Finally, what about accessors imposed on constructors and destructors? I've always just made them public. Would it ever make sense to make a constructor or destructor private or even protected? I could see value in making a constructor meant only to be used solely by the class' factory methods, and nowhere else. Then however, why would anyone ever want to reduce access to a destructor?

Share this post


Link to post
Share on other sites
Advertisement

Hey guys,

 

I had a lengthy response going yesterday, but I ran out of time to finish it. Then, I lost the post. I'll try to rewrite it later.

 

I'd like to mention is that it sounds like inheritance should be used sparingly in C++ whereas other languages like Objective-C and C# thrive off of it. Objective-C requires everything you make to inherit from something else that all derives down to NSObject, in fact. I see the points in presented protected constructors and destructors for cases regarding factories and templates make sense.

 

@Servant of the Lord: You bring up many good points, and thank you for pointing out C++-11's final keyword. I forgot about that. You also bring up good points about trusting others to be reasonably responsible with code written by others. I mean, if OpenGL gets incorrect information, you'll either get a crash or undefined/unexpected results after all.

 

@Hodgman: The idea of having leaner constructors make quite a bit of sense. I usually try to do lots of initial setting stuff in my constructors, and file loading. I should probably move the file loading to a factory.

 

@dmatter: You also bring up many good points, and reinforce what others have said in this post. It's sinking in for me now lol. You also bring up interesting stuff about protected destructors that I'd like to ask you more about when I have time to test out what you've said (and think it through --lots to have sink in there for me haha).

Share this post


Link to post
Share on other sites

I'd like to mention is that it sounds like inheritance should be used sparingly in C++ whereas other languages like Objective-C and C# thrive off of it.

 

I wouldn't say "sparingly". Inheritance is used alot, but it's just not the first tool you reach for.

 

In C++, you want to prefer composition over inheritance. But inheritance isn't avoided, it's just another tool in your toolbox when you need something more.

It depends on your program's overall architecture, though. In some C++ libraries, for certain purposes, it's good to make alot of objects inherit a single base class.

 

A common use of this is with GUI widget libraries - because you often want to write run-time* generic "for every child widget" code, since many 'widgets' contain children that also are widgets but without knowing what kind of widgets the children are.

 

*If you want compile-time generic functionality, you use templates - another very powerful tool.

 

C++ has a design goal: "Don't pay for what you don't need", that makes the language lightning fast. So when I say virtual inheritance "costs" extra, I don't want to give the wrong impression that virtual inheritance is slow. It's not. It's still lightning fast, but slightly less lightning fast than not using virtual. Other languages often opt-in to pay the same costs 100% of the time, whereas C++ says, "Only pay for it when you actually want it".

 

This influences alot of how the language is designed, and how the language is used.

Share this post


Link to post
Share on other sites

Some examples for the original questions; I have these two helper classes:


class NonCopyable
{
public:
NonCopyable(){}
private:
NonCopyable( const NonCopyable& );
NonCopyable& operator=( const NonCopyable& );
};

class NoCreate
{
private:
NoCreate(){}
NoCreate( const NoCreate& );
NoCreate& operator=( const NoCreate& );
};
I use NonCopyable when I don't have a need for a class to be able to make copies of itself and/or when implementing copying would be too complex. I'm a big fan of YAGNI and KISS, so if I don't need a copy operator, I don't write it.
class SomeComplexThing : NonCopyable
{
public:
SomeComplexThing(const char* filenameToOperateOn);
~SomeComplexThing();
};
Without inheriting from NonCopyable, the above class would be in breach of the rule of three. Inheriting NonCopyable satisfies the rule, while not requiring me to actually implement copying. If a user tries to copy an object of this class, they'll get a compile time error saying it's not allowed.
 
NoCreate is a lot more esoteric. I use it as a helper for a type of non-polymorphic interfaces, something similar to the PIMPL pattern.
//header
class Doodad : NoCreate
{
public:
int GetHealth();
std::string GetName();
};
//user cannot create/destroy Doodads, only get pointers to the ones owned by the complex thing
class SomeComplexThing : NonCopyable
{
public:
SomeComplexThing(const char* filename);
int GetDoodadCount() const;
Doodad* GetDoodadByIndex(int i) const;
private:
void* buffer;
};

//cpp file
SomeComplexThing::SomeComplexThing(const char* fn) : bufffer(LoadFile(fn)) {}
SomeComplexThing::~SomeComplexThing() { FreeFile(buffer); }

struct DoodadFile //implementation of the Doodad interface
{
int count;
struct Item
{
int health;
char name[64];
} items[];
};

int SomeComplexThing::GetDoodadCount() const
{
DoodadFile* d = (DoodadFile*)buffer;
return d->count;
}
Doodad* SomeComplexThing::GetDoodadByIndex(int i) const
{
DoodadFile* d = (DoodadFile*)buffer;
if( i >=0 && i < d->count )
return (Doodad*)&d->items[i];
return 0;
}

int Doodad::GetHealth()
{
DoodadFile::Item* self = (DoodadFile::Item*)this;
return self->health;
}
std::string Doodad::GetName()
{
DoodadFile::Item* self = (DoodadFile::Item*)this;
return std::string(self->name);
}

Small point of order - modern C++ lets you delete functions with "=delete". This implements "non-copyable" in a much cleaner way, and one that the compiler can catch at compile time, rather then link time.

class DoNotCopyMe
{
public:
DoNotCopyMe() = default; // Use the compiler-provided defaults
~DoNotCopyMe() = default;

DoNotCopyMe(const DoNotCopyMe&) = delete; // Compiler will error at compile time if either of these are used
DoNotCopyMe& operator=(const DoNotCopyMe&) = delete;
};
(This doesn't cover movable types, but that's a whole 'nother kettle o' fish) Edited by SmkViper

Share this post


Link to post
Share on other sites

But for what it's worth, C# has good support for inheritance and amazing support for composition as well.


What does C# have that makes composition easier than in say C++?

Share this post


Link to post
Share on other sites


Another downside to be aware of though, is when you use a complex constructor (and no default constructor), you're limiting your class from being usable in some places, e.g. std::vector will no longer work (but std::vector will).
*>

That's not true.  The only requirement for use in std::vector is that a type is assignable and copyable (and in modern C++, those restrictions are relaxed to simply movable), neither of which requires a default constructor.

 

What may be more likely is that if you have a class like an asset loader, it's not copyable so you can't stick it into a standard container.  That really has nothing to do with the complexity of the constructor but rather either the class invariants (eg. "there can be only one of these") or the size of its data (eg. copying is too expensive).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!