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?
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.