2

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?

Community
  • 1
  • 1
haelix
  • 4,245
  • 4
  • 34
  • 56
  • 3
    `class MyClass { int a = 0; int b = 0; int c = 0; /* your constructors */ };`. That can even be an aggregate since C++14. – Kerrek SB Apr 02 '15 at 12:44
  • *"even if we declare one (private), it needs to explicitly zero members one-by-one"* Not if you default it `= default`. – dyp Apr 02 '15 at 12:47
  • @dyp From what I know, the defaulted no-arg c-tor will not zero the members? – haelix Apr 02 '15 at 14:07
  • 1
    @haelix Yes, but it allows value-initialization to perform zero-initialization. That is, `MyClass obj{};` will zero-initialize `obj` if `MyClass`'s default ctor is defaulted. – dyp Apr 02 '15 at 16:57
  • @dyp that is very interesting for my case, I'll update the question because I need more info / exact source for that case – haelix Apr 03 '15 at 06:46
  • @haelix It is case 2) on cppreference: a defaulted default ctor is **not** user-provided. [dcl.fct.def.default]/5 "A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration." – dyp Apr 03 '15 at 13:26
  • Allright so you're referring to case 2) "(since C++14)" - that makes sense. What I'd like to know now - if you default the default c-tor on first declaration, thereby enabling value-initialization, is it possible to write another c-tor (with 1+ argument(s) ) and use `constructor delegation` to the defaulted c-tor in order to value-initialize? My guess is that zero-initialization will not happen in this case (in the sense that it's not guaranteed by the standard). – haelix Apr 06 '15 at 13:56
  • 1
    Yes, it seems constructor delegation will not invoke value-initialization when delegating to the default constructor, but simply call that constructor. You can however achieve something similar by adding a layer of abstraction, i.e. `class MyClass { struct { int a; int b; int c; } data; public: MyClass() = default; MyClass(int a) : data() { data.a = a; } };` – dyp Apr 11 '15 at 14:48
  • @dyp Thanks for the comments. Would you care to write an answer that supports my `option #1` in conjunction with your arguments why (and when) that works. From all info I gather, that would be the preferred option in my specific case. So basically, add c-tor defaulted on first declaration, invoke that using `*this = MyClass{};`, and subsequently set your selected data members to what you'd like. Pity that we can't use c-tor delegation, since the solution now requires an assignment. – haelix Apr 14 '15 at 09:00

3 Answers3

4

What about using in-class initialization?

class Foo
{
    int _a{}; // zero-it
    int _b{}; // zero-it
public:
    Foo(int a): _a(a){} // over-rules the default in-class initialization
};
vsoftco
  • 55,410
  • 12
  • 139
  • 252
3

Option 4 and 5:

option 4:


MyClass(int a) :a(a), b(0), c(0)
  {
  }

option 5:


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

      MyClass(int a) : a(a) {
      }
  }
coyotte508
  • 9,175
  • 6
  • 44
  • 63
1

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.

dyp
  • 38,334
  • 13
  • 112
  • 177
  • Nice detailed answer, thanks. Maybe someone will add more useful thoughts regarding the last paragraph "constructor delegation" - that would be syntactically the best approach and something I believe the standard should offer. – haelix Apr 15 '15 at 07:16
  • @haelix I filed a g++ bug report. – dyp Apr 20 '15 at 16:25