Use a factory function if you need "virtual behavior" during initialization
Reason
If the state of a base class object must depend on the state of a derived part of the object, we need to use a virtual function (or equivalent) while minimizing the window of opportunity to misuse an imperfectly constructed object.
Note
The return type of the factory should normally be unique_ptr by default; if some uses are shared, the caller can move the unique_ptr into a shared_ptr. However, if the factory author knows that all uses of the returned object will be shared uses, return shared_ptr and use make_shared in the body to save an allocation.
Example, bad
class B {
public:
B()
{
/* ... */
f(); // BAD: C.82: Don't call virtual functions in constructors and destructors
/* ... */
}
virtual void f() = 0;
};
Example
class B {
protected:
class Token {};
public:
explicit B(Token) { /* ... */ } // create an imperfectly initialized object
virtual void f() = 0;
template<class T>
static shared_ptr<T> create() // interface for creating shared objects
{
auto p = make_shared<T>(typename T::Token{});
p->post_initialize();
return p;
}
protected:
virtual void post_initialize() // called right after construction
{ /* ... */ f(); /* ... */ } // GOOD: virtual dispatch is safe
};
class D : public B { // some derived class
protected:
class Token {};
public:
explicit D(Token) : B{ B::Token{} } {}
void f() override { /* ... */ };
protected:
template<class T>
friend shared_ptr<T> B::create();
};
shared_ptr<D> p = D::create<D>(); // creating a D object
make_shared requires that the constructor is public. By requiring a protected Token the constructor cannot be publicly called anymore, so we avoid an incompletely constructed object escaping into the wild. By providing the factory function create(), we make construction (on the free store) convenient.
Note
Conventional factory functions allocate on the free store, rather than on the stack or in an enclosing object.
See also: Discussion