Search code examples
c++11initializationzerodefault-constructorvalue-initialization

How to make sure your object is zero-initialized?


Update: I'm looking to see if there's a way to zero-initialize the entire class at once, because technically, one can forget adding a '= 0' or '{}' after each member. One of the comments mentions that an explicitly defaulted no-arg c-tor will enable zero-initialization during value-initialization of the form MyClass c{};. Looking at http://en.cppreference.com/w/cpp/language/value_initialization I'm having trouble figuring out which of the statements specify this.

Initialization is a complex topic now since C++11 has changed meaning and syntax of various initialization constructs. I was unable to gather good enough info on it from other questions. But see, for example, Writing a Default Constructor Forces Zero-Initialization?.

The concrete problem I'm facing is: I want to make sure members of my classes are zeroed out both for (1) classes which declare a default c-tor, and for (2) those which don't.

For (2), initializing with {} does the job because it's the syntax for value-initialization, which translates to zero-initialization, or to aggregate initialization if your class is an aggregate - case in which members for which no initializer was provided (all!) are zero-initialized.

But for (1) I'm still not sure what would be the best approach. From all info I gather I learned that if you provide a default c-tor (e.g. for setting some of the members to some values), you must explicitly zero remaining members, otherwise the syntax MyClass c = MyClass(); or the C++11 MyClass c{}; will not do the job. In other words, value-initialization in this case means just calling your c-tor, and that's it (no zero-ing).

You run into the same situation if you declare a c-tor that takes values, and sets those values to a subset of the members, but you'd like other members to be zero-ed: there is no shorthand for doing it - I'm thinking about 3 options:

class MyClass
{
  int a;
  int b;
  int c;

  MyClass(int a)
  {
    this->a = a;
    // now b and c have indeterminate values, what to do? (before setting 'a')


    // option #1
    *this = MyClass{}; // we lost the ability to do this since it requires default c-tor which is inhibited by declaring this c-tor; even if we declare one (private), it needs to explicitly zero members one-by-one

    // option #2
    std::memset(this, 0, sizeof(*this)); // ugly C call, only works for PODs (which require, among other things, a default c-tor defaulted on first declaration)

    // option #3
    // don't declare this c-tor, but instead use the "named constructor idiom"/factory below
  }


  static MyClass create(int a)
  {
    MyClass obj{}; // will zero-initialize since there are no c-tors
    obj.a = a;
    return obj;
  }
};

Is my reasoning correct? Which of the 3 options would you choose?


Solution

  • In my humble opinion, the simplest way to ensure zero-initialization is to add a layer of abstraction:

    class MyClass
    {
        struct
        {
            int a;
            int b;
            int c;
        } data{};
    public:
        MyClass(int a) : data{a} {}
    };
    

    Moving the data members into a struct lets us use value-initialization to perform zero-initialization. Of course, it is now a bit more cumbersome to access those data members: data.a instead of just a within MyClass. A default constructor for MyClass will perform zero-initialization of data and all its members because of the braced-initializer for data. Additionally, we can use aggregate-initialization in the constructors of MyClass, which also value-initializes those data members which are not explicitly initialized.

    The downside of the indirect access of the data members can be overcome by using inheritance instead of aggregation:

    struct my_data
    {
        int a;
        int b;
        int c;
    };
    
    class MyClass : private my_data
    {
        MyClass() : my_data() {}
    public:
        MyClass(int a) : MyClass() { this->a = a; }
    };
    

    By explicitly specifying the base-initializer my_data(), value-initialization is invoked as well, leading to zero-initialization. This default constructor should probably be marked as constexpr and noexcept. Note that it is no longer trivial. We can use initialization instead of assignment by using aggregate-initialization or forwarding constructors:

    class MyClass : private my_data
    {
    public:
        MyClass(int a) : my_data{a} {}
    };
    

    You can also write a wrapper template that ensures zero-initialization, thought the benefit is disputable in this case:

    template<typename T>
    struct zero_init_helper : public T
    {
        zero_init_helper() : T() {}
    };
    
    struct my_data
    {
        int a;
        int b;
        int c;
    };
    
    class MyClass : private zero_init_helper<my_data>
    {
    public:
        MyClass(int a) { this->a = a; }
    };
    

    Having a user-provided constructor, zero_init_helper no longer is an aggregate, hence we cannot use aggregate-initialization any more. To use initialization instead of assignment in the ctor of MyClass, we have to add a forwarding constructor:

    template<typename T>
    struct zero_init_helper : public T
    {
        zero_init_helper() : T() {}
    
        template<typename... Args>
        zero_init_helper(Args&&... args) : T{std::forward<Args>(args)...} {}
    };
    
    class MyClass : private zero_init_helper<my_data>
    {
    public:
        MyClass(int a) : zero_init_helper(a) {}
    };
    

    Constraining the constructor template requires some is_brace_constructible trait, which is not part of the current C++ Standard. But this already is a ridiculously complicated solution to the problem.


    It is also possible to implement your option #1 as follows:

    class MyClass
    {
      int a;
      int b;
      int c;
    
      MyClass() = default; // or public, if you like
    
    public:
      MyClass(int a)
      {
        *this = MyClass{}; // the explicitly defaulted default ctor
                           // makes value-init use zero-init
        this->a = a;
      }
    };
    

    What about constructor delegation?

    class MyClass
    {
      int a;
      int b;
      int c;
    
      MyClass() = default; // or public, if you like
    
    public:
      MyClass(int a) : MyClass() // ctor delegation
      {
          this->a = a;
      }
    };
    

    [class.base.init]/7 suggests that the above example shall invoke value-initialization, which leads to zero-initialization since the class does not have any user-provided default constructors [dcl.init]/8.2. Recent versions of clang++ seem to zero-initialize the object, recent versions of g++ do not. I've reported this as g++ bug #65816.