Search code examples
c++c++11member-initialization

C++ - Mixing default member initializers and member initialization lists - bad idea?


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?


Solution

  • 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 the header_ pointer is dubious but defensible (though it's initialized incorrectly; you need to use placement-new to satisfy C++'s object model). But data_length_ can be computed on an as-needed basis.

    The fewer members you have, the less chance for confusion about how they get initialized.