It appears that sometimes it is a good idea to keep pointer-to-object members rather than object members in a class. Three good occasions when we would want to do that are:
- When working with incomplete types. Classic example is when implementing the Pimpl idiom.
- When we want our class to be copyable and the member object is of a non-copyable class.
- When making a member out of a class that does not have a default constructor.
To elaborate on the second point, the copy construction and copy assignment of a class usually involves copying/assigning the parent classes and then the members. This is what the compiler would do with the implicit copy constructor and copy assignment operator. But even if we implement it ourselves, it should almost always be constructing/assigning parents and members, and in that order. If the members are of a non-copyable class, then this means that the class that we define also becomes non-copyable.
To elaborate on the third point, if we make use of an object member out of a class that does not have a default constructor, then, while constructing our class, we have to make sure that we call the appropriate constructor for the member with appropriate arguments. This can get a bit restrictive especially with templates:
template <typename T>
class CustomClass1 {
private:
T data_;
...
};
CustomClass1 can only be specialized with types that have default constructors. Instead, CustomClass2 below can be specialized for any type:
template <typename T>
class CustomClass2 {
private:
T* pData_;
...
};
As an aside, two things to keep in mind when defining classes with pointer-to-object members:
- It is probably a good idea to make use of boost::scoped_ptr or boost::shared_ptr instead of raw pointers, so that deletion is handled automatically.
- Always remember to provide one’s own declarations (with or without definitions) of the copy constructor and copy assignment operator for the class that has pointer-to-object members, as, the implicitly generated ones almost certainly will not be what we want (and can very likely be dangerous). Note that if we used a scoped_ptr, this is automatically taken care of, because, as indicated in point 2 above, since scoped_ptr is a non-copyable class, the enclosing class itself becomes non-copyable (unless you intentionally make it copyable and deal with non-copyable members somehow).
Now, I guess the other way to look at point 3 is to always provide a default constructor for a class unless it absolutely does not make sense. It helps in many ways:
- It alleviates the restriction of having to keep pointer members (if this were the only reason, like in point 3 above).
- You can create dynamic arrays as follows, only if there is a default constructor:
CustomClass* dynamic_array = new CustomClass[20]; //only possible if CustomClass has a default constructor.
//For static arrays you could invoke parametrized constructors using the initialization syntax, provided there is an accessible copy constructor:
CustomClass static_array[20]; //only possible with default constructors.
CustomClass static_array_param[2] = { CustomClass(...), CustomClass(...) }; //parametrized constructors invoked.
//Similarly for container classes. Following vector works with parametrized constructors provided there is an accessible copy constructor (which is a must for elements in a vector anyway):
std::vector<CustomClass> v(10, CustomClass(...));
// But notice that, strictly speaking, you are creating temporary object(s) with the parametrized constructor and then copy-constructing the elements in the static array or vector using the temporaries.
Interestingly, many standard library classes provide default constructors although it might appear to not make much sense.
#include <fstream>
ofstream file1("/tmp/tempfile"); //parametrized constructor makes sense.
//However, we can achieve the same effect with the following:
ofstream file2; //default constructor.
//Nothing constructive you can do with file2 until you call the open method:
file2.open("/tmp/tempfile");
...
Updates:
1. Corrections in the section about default constructors, with regard to static arrays and vectors.
2. Reference: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.5
3. Reference: http://www.acm.org/crossroads/xrds1-4/ovp.html