Constructors, Factory Methods and Destructor Questions

Started by
15 comments, last by Servant of the Lord 9 years, 4 months ago

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?

Advertisement

Is it always necessary to provide the default constructor (zero arguments) for your classes?

Not always. In some rarer cases, it makes sense to not even expose any public constructor (for example, if you want to enforce creation through a factory).

In general, it's good practice to have a default constructor. I think it's even necessary for some of the standard containers.

Would it ever make sense to make a constructor or destructor private or even protected?

Yep, see my previous comment about controlling who can construct classes.

Then however, why would anyone ever want to reduce access to a destructor?

I've never personally ran into a situation where that was necessary for a destructor, and none immediately come to mind, but I wouldn't totally dismiss the possibility that in some rare circumstance it might occasionally be useful.

Is it good practice to always have virtual destructors?

No, not really. By adding virtual destructors to classes that don't already have virtual functions, you automatically require the class to have a vtable, which adds a small performance and size cost to that class. It also guarantees that they are no longer POD-types, which limits some optimizations you can take advantage of in certain circumstances.

You can write your code to protect the programmer from common mistakes, but it's impossible to write your code to prevent the programmer from every mistake, so you shouldn't try so hard to bend backwards to prevent people from doing things like inheriting from classes that clearly weren't meant to be inherited from. That's not something that "accidentally" occurs, that's an intentional decision someone makes that is a bad decision.

Write your code to prevent accidents, but don't worry too much about preventing deliberate stupidity. You'll never be able to prevent it all the time. happy.png

"A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools." - Mostly Harmless (HHGTTG), Douglas Adams

What if I wanted to extend my object?
[...]
I always do this in case I want to extend that class.
[...]
I wouldn't be able to extend my [...], if I wanted to add [...]


Design your classes well, but don't so over-design them that you limit their usability in other ways. Design them well to meet the current requirements you have, and the immediately foreseeable requirements, not the theoretically it-might-someday-be-possible requirements.

Usually, I use virtual inheritance when I want two or more types of objects to pretend to be the same type (polymorphism). If I want to extend a class for non-polymorphic reasons, I usually just modify the original class to add what I want, or create a new class that contains the original class internally (if it makes sense that ClassB has-a ClassA), or create a new class with the common functionality of both classes refactored into functions that can be shared between both classes.

Since C++ doesn't allow us to declare a class as "sealed" or "final"

We're in 2014, so you should be using C++11 (standardized in 2011), unless you're forced not to for some reason. In C++11 you can declare classes 'final', as well as declare individual virtual functions 'final'.

If a class is designed to be used as a "base class" for use with concrete/implementation inheritance (AKA 'extends' inheritance) then it's common for the constructor (and/or destructor) to be protected if this base class is not functional without being extended first.

Making destructors private makes it impossible to use the delete keyword on those objects. If you then make a factory class a friend then you can force people into calling factory::release instead of deleting the object themselves.

If the class doesn't have virtual methods and inheritance, there's no need for a virtual destructor.
If a class is polymorphic, i.e. has other virtual functions and is accessed via pointers of types other than it's actual type, then the rule of thumb is to always have a virtual destructor.
The actual issue that virtual destructors solve, is it allows users to use the delete keyword using a pointer to a parent/interface class. Without a virtual destructor, that parent class' destructor will be called, but the derived type's one won't.

Inheritance is very rare in a lot of code, so virtual destructors will be in those same areas.

Constructors that do a lot of work are a bad idea if those operations can fail.
Otherwise it's just a code style choice; people will argue either way.

Is it always necessary to provide the default constructor (zero arguments) for your classes?

No it is not required to have a default constructor.
It's not necessarily good practice to always have a default constructor either. Only if your class would benefit from one. Just like any other piece of code: Only write it if you need it.

I see far too many default constructors that leave objects in a broken, uninitialised or just meaningless state; all because they think they should always add a default constructor.

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.

File loading in constructors is done by the standard file-streams themselves, so I wouldn't beat yourself up over it!

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.

Inheritance is something to use sparingly in the first place. When it is used there are special design considerations to make - and that's what you're seeing here.

As a rule of thumb: If your class is intended to be a concrete (fully instantiable) implementation of something in its own right then it should not also be used as a base class. Base classes should be abstract (non-instantiable, partial implementations) or without any implementation at all which is true in the case of an interface.

With that in mind, the answer to the question "What if I wanted to extend my object?" is: Don't.

Then, there are destructors... Is it good practice to always have virtual destructors?

Nope. Only if you need polymorphic destruction. Generally if you have other virtual functions then you can reasonably expect to need polymorphic destruction too, so make the destructor virtual.


Since C++ doesn't allow us to declare a class as "sealed" or "final"

Modern C++ has a final keyword.
Other than that, you could make the constructor private and use the named-constructor idiom. Not ideal though. I would only go that far if there was any particular risk of (and danger caused by) extending a class.

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?

Well I've just mentioned making constructors private to prevent people inheriting from it.
A protected constructor is useful in the same way that regular protected functions are - they allow derived classes to access functionality that you don't want to expose publically.

Private destructors are rare (I have never once needed to write a private destructor myself) and most cases where they do exist could probably have been dealt with differently. But they get used when a class needs to handle deletion in a special way, for example a class that expects to be allocated on the heap and freed by a specially collaborating reference-counting handle. It might hide its destructor to prevent manual destruction.

Protected destructors are used for non-polymorphic base-classes: A base class that isn't intended to be used polymorphically. This would be the case if the base-class expects to be inheritted from but only to inject something into that derived class (e.g. a templated base-class comprised only of typedefs). In this case polymorphic deletion of this base-class isn't expected so a virtual destructor isn't needed either (and adding one "just to be safe" would incur the downside of a vtable) and making the destructor protected will catch the misuse at compile-time.

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).

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.

@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.

I left it at 'people will argue either way', but I personally am a fan of complex constructors happy.png
For a general purpose file loading library, yeah, I'd prefer a factory function that can either return a file object, or return null/error.

But for a game asset loading library, there's no errors that can occur -- If an asset is missing from the game directory, you blame the user for deleting your data files, and then you crash. So I'd have no issues at all with the constructor of a GameAssetFile class doing complex work like obtaining file pointers, kicking off asynchronous HDD reads, etc...

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<GameAssetFile> will no longer work (but std::vector<GameAssetFile*> will).

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);
}

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.

I would say that in every OO language, inheritance should be use sparingly, but unfortunately many people suffer from inheritance-addiction.
As far as I'm concerned "prefer composition over inheritance" is one of the core rules of OO design. I won't rant again because I just posted one here laugh.png
But for what it's worth, C# has good support for inheritance and amazing support for composition as well.

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)

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++?


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).

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement