This is partly a style question, partly a correctness question. Submit the following sample (a strip-down of a class which deals with a block of data that contains an embedded header):
class Foo {
public:
Foo(size_t size)
: scratch_(new uint8_t[header_length_ + size]),
size_(header_length_ + size) {
}
~Foo() {
delete[] scratch_;
}
Foo(const Foo&) = delete; // Effective C++
void operator=(const Foo&) = delete; // Effective C++
protected:
struct Header {
uint32_t a, b, c, d;
};
uint8_t * const scratch_;
size_t const size_;
Header * const header_ = reinterpret_cast<Header *>(scratch_);
static constexpr size_t header_length_ = sizeof(Header);
static constexpr size_t data_offset_ = header_length_;
size_t const data_length_ = size_ - data_offset_;
};
First, technical correctness... is it correct that as written, scratch_
and size_
will be initialized first, then header_
, then data_length_
? (constexpr
items are compile-time literal constants and don't figure into initialization order.) Is it also correct that how the initializer is declared, be it default member initialization (int foo = 5
) or a member initializer list, has no impact on initialization order, but rather, what matters is solely the order in which members are declared? I found this answer, citing the ISO spec regarding initialization order, and what I gathered is that it's unimportant that scratch_
and size_
appear in the member initialization list versus other members that are given default member initializers; it only matters that scratch_
and size_
are declared before the other members. Presumably if scratch_
and size_
were declared last, then header_
and data_length_
would (undesirably/incorrectly) be initialized first.
Style question... is it bad style to mix these two initialization styles? My approach is that items in the member initialization list (scratch_
, size_
) depend on the argument(s) passed into the constructor, while the remaining class members are derived from other class members. Obviously, if an initializer depends on a constructor argument, then it has to go into a member initialization list. Should I throw all initializers into the member initialization list regardless, and abandon default member initializers? IMO, that may make the code a little harder to follow. Thoughts?
The presence of default member initializers changes nothing about the order in which subobjects of a type are initialized. It will always be in declaration order.
Style is up to you. The more constructors you have, the more you gain by using DMIs, since you're not repeating initialization that doesn't change. At the same time, if you start making constructors that override DMIs, that can create confusion about what the object's initial state is. The key is to try not to be surprising about what's going on.
In your particular case however, I'd say that you have too many variables. Your array should just be a
std::vector<uint8_t>
. Having theheader_
pointer is dubious but defensible (though it's initialized incorrectly; you need to use placement-new to satisfy C++'s object model). Butdata_length_
can be computed on an as-needed basis.The fewer members you have, the less chance for confusion about how they get initialized.