Non-Rules and myths
NR.5
Don't use two-phase initialization
Reason
Splitting initialization into two leads to weaker invariants, more complicated code (having to deal with semi-constructed objects), and errors (when we didn't deal correctly with semi-constructed objects consistently).
Note
Sometimes also called two-stage construction.
Example, bad
// Old conventional style: many problems
class Picture
{
int mx;
int my;
int * data;
public:
// main problem: constructor does not fully construct
Picture(int x, int y)
{
mx = x; // also bad: assignment in constructor body
// rather than in member initializer
my = y;
data = nullptr; // also bad: constant initialization in constructor
// rather than in member initializer
}
~Picture()
{
Cleanup();
}
// ...
// bad: two-phase initialization
bool Init()
{
// invariant checks
if (mx <= 0 || my <= 0) {
return false;
}
if (data) {
return false;
}
data = (int*) malloc(mx*my*sizeof(int)); // also bad: owning raw * and malloc
return data != nullptr;
}
// also bad: no reason to make cleanup a separate function
void Cleanup()
{
if (data) free(data);
data = nullptr;
}
};
Picture picture(100, 0); // not ready-to-use picture here
// this will fail..
if (!picture.Init()) {
puts("Error, invalid picture");
}
// now have an invalid picture object instance.
Example, good
class Picture
{
int mx;
int my;
vector<int> data;
static int check_size(int size)
{
// invariant check
Expects(size > 0);
return size;
}
public:
// even better would be a class for a 2D Size as one single parameter
Picture(int x, int y)
: mx(check_size(x))
, my(check_size(y))
// now we know x and y have a valid size
, data(mx * my) // will throw std::bad_alloc on error
{
// picture is ready-to-use
}
// compiler generated dtor does the job. (also see C.21)
// ...
};
Picture picture1(100, 100);
// picture1 is ready-to-use here...
// not a valid size for y,
// default contract violation behavior will call std::terminate then
Picture picture2(100, 0);
// not reach here...
Alternative
- Always establish a class invariant in a constructor.
- Don't define an object before it is needed.