What (not) to do in a constructor
The most common mistake to do in a constructor as well as in a destructor, is to use polymorphism. Polymorphism often does not work in constructors !
e.g.:
class A
{
public:
A(){ doA();}
virtual void doA(){};
}
class B : public A
{
public:
virtual void doA(){ doB();};
void doB(){};
}
void testB()
{
B b; // this WON'T call doB();
}
this is because the object B is not yet constructed while performing the constructor of the mother class A... thus impossible for it to call the overriden version of void doA();
An example where polymorphism will work in constructor:
class A
{
public:
void callAPolymorphicBehaviour()
{
doOverridenBehaviour();
}
virtual void doOverridenBehaviour()
{
doA();
}
void doA(){}
};
class B : public A
{
public:
B()
{
callAPolymorphicBehaviour();
}
virtual void doOverridenBehaviour()
{
doB()
}
void doB(){}
};
void testB()
{
B b; // this WILL call doB();
}
This time, the reason behind is: at the time the virtual
function doOverridenBehaviour()
is invoked, the object b is already initialized (but not yet constructed), this means that its virtual table is initialized, and thus can perform polymorphism.
- Don't call
delete this
or the destructor in the constructor. - Don't use init()/cleanup() members. If you have to call init() every time you create an instance, everything in init() should be in the constructor. The constructor is meant to put the instance into a consistent state which allows any public member to be called with a well-defined behavior. Similarly for cleanup(), plus cleanup() kills RAII. (However, when you have multiple constructors, it's often useful to have a private init() function that's called by the them.)
- Doing more complex things in constructors is okay, depending on the classes' intended use and your overall design. For example, it wouldn't be a good idea to read a file in the constructor of some kind of Integer or Point class; users expect those to be cheap to create. It's also important to consider how file-accessing constructors will impact your ability to write unit tests. The best solution is usually to have a constructor that just takes the data that it needs to construct the members and write a non-member function that does the file parsing and returns an instance.
Complex logic and constructor do not always mix well, and there are strong proponents against doing heavy work in a constructor (with reasons).
The cardinal rule is that the constructor should yield a fully usable object.
class Vector
{
public:
Vector(): mSize(10), mData(new int[mSize]) {}
private:
size_t mSize;
int mData[];
};
It does not mean a fully initialized object, you may defer some initialization (think lazy) as long as the user does not have to think about it.
class Vector
{
public:
Vector(): mSize(0), mData(0) {}
// first call to access element should grab memory
private:
size_t mSize;
int mData[];
};
If there is heavy work to be done, you might choose to proceed with a builder method, that will do the heavy work prior to calling the constructor. For example, imagine retrieving settings from a database and building a setting object.
// in the constructor
Setting::Setting()
{
// connect
// retrieve settings
// close connection (wait, you used RAII right ?)
// initialize object
}
// Builder method
Setting Setting::Build()
{
// connect
// retrieve settings
Setting setting;
// initialize object
return setting;
}
This builder method is useful if postponing the construction of the object yields a significant benefit. From example if the objects grab a lot of memory, postponing the memory acquisition after tasks that are likely to fail may not be a bad idea.
This builder method implies Private constructor and Public (or friend) Builder. Note that having a Private constructor imposes a number of restrictions on the usages that can be done of a class (cannot be stored in STL containers, for example) so you might need to merge in other patterns. Which is why this method should only be used in exceptional circumstances.
You might wish to consider how to test such entities too, if you depend on an external thing (file / DB), think about Dependency Injection, it really helps with Unit Testing.