4

Is there a simpler way for a class's constructor to specify that all members of built-in type should be zero-initialized?

This code snippet came up in another post:

struct Money
{
    double amountP, amountG, totalChange;
    int twenty, ten, five, one, change;
    int quarter, dime, nickel, penny;

    void foo();
    Money()  {}
};

and it turned out that the problem was that the object was instantiated via Money mc; and the variables were uninitialized.

The recommended solution was to add the following constructor:

Money::Money()
  : amountP(), amountG(), totalChange(),
    twenty(), ten(), five(), one(), change()
    quarter(), dime(), nickel(), penny()
{
}

However, this is ugly and not maintenance-friendly. It would be easy to add another member variable and forget to add it to the long list in the constructor, perhaps causing a hard-to-find bug months down the track when the uninitialized variable suddenly stops getting 0 by chance.

Wolf
  • 8,482
  • 7
  • 48
  • 92
M.M
  • 130,300
  • 18
  • 171
  • 314
  • @chris Good idea if there is no user-defined constructor, but could be awkward if the class also has some other code it wants to run on construction – M.M Apr 09 '14 at 22:49
  • My apologies, I didn't realize how bad that was until a moment ago. – chris Apr 09 '14 at 22:53

3 Answers3

8

For something working in C++98, you can use Boost's value_initialized: (live example)

#include <boost/utility/value_init.hpp>
...
struct Money
{
    boost::value_initialized<double> amountP, amountG, totalChange;
    boost::value_initialized<int> twenty, ten, five, one, change;
    boost::value_initialized<int> quarter, dime, nickel, penny;

    void foo();
    Money()  {/*every member is 0*/}
};
chris
  • 55,166
  • 13
  • 130
  • 185
  • Good answer .. am accepting Ben's as it can be done without `boost` but yours is solid also – M.M Apr 10 '14 at 01:53
  • definitely (+) a good use for templates, a trivial implementation is possible without using boost. – Wolf Oct 10 '14 at 13:44
8

You can use a subobject to initialize en masse. A member works, but then you need to qualify all access. So inheritance is better:

struct MoneyData
{
    double amountP, amountG, totalChange;
    int twenty, ten, five, one, change;
    int quarter, dime, nickel, penny;
};

struct Money : MoneyData
{
    void foo();
    Money() : MoneyData() {} /* value initialize the base subobject */
};

Demonstration (placement new is used to make sure the memory is non-zero before object creation): http://ideone.com/P1nxN6

Contrast with a slight variation on the code in the question: http://ideone.com/n4lOdj

In both of the above demos, double members are removed to avoid possible invalid/NaN encodings.

Ben Voigt
  • 260,885
  • 36
  • 380
  • 671
  • Not a bad idea, all things considered – M.M Apr 09 '14 at 23:08
  • @M.M: Unfortunately, I don't really trust the demonstration, since it prints zero also for the code in the question. g++ must be zeroing during placement new. – Ben Voigt Apr 09 '14 at 23:10
  • Nice, and this avoids problems that a proxy type can run into when being used like the underlying type. – chris Apr 09 '14 at 23:10
  • @BenVoigt This solution builds on the fact that C++-specific syntax forces the compiler to initialize members, is this right? – Wolf Oct 10 '14 at 12:58
  • @Wolf: I'm not sure I understand your question. Are you asking whether the technique applies to C? There are two things involved here: One, a type with user-defined constructors can never be uninitialized, either the user added a default constructor, or there is no default constructor. Two, when the constructor is invoked during object creation, subobjects get initialized. Here the value-initialization syntax is used for the base subobject. Because the base class is an aggregate, value initialization causes value initialization of each individual member, which sets primitive types to zero – Ben Voigt Oct 10 '14 at 13:21
  • I see that a `struct` being initialized in a way, `MoneyData()` , that cannot be used in C, because initializer lists don't exist there. So the compiler can detect safely, that the programmer is aware the extra-cost... ...I hate [these initialization struggles](http://stackoverflow.com/q/19905686/2932052) ;) – Wolf Oct 10 '14 at 13:29
  • @Wolf: C doesn't have behavior provided by the type *at all*. You say "initializer lists don't exist there" (and you're correct), but miss the bigger picture. **Constructors** don't exist there. An object is not in control of its own data in C; initialization is *always* up to the user. – Ben Voigt Oct 10 '14 at 13:32
  • @BenVoigt So I can be sure that if I embed a C struct in a class with a constructor, that it is initialized? I thought this being only the case if explicitely mentioned in a constructor? – Wolf Oct 10 '14 at 13:34
  • @Wolf: With respect to your linked question, I feel your pain because I work in embedded systems where the compilers often are non-conformant (or conform to an old standard). You're on the desktop; I encourage you to switch to a real compiler. I'm pretty sure Borland compilers have always been in the business of creating their own similar language (look at Delphi vs Pascal). – Ben Voigt Oct 10 '14 at 13:36
  • @Wolf: It will be default-initialized if you don't specify otherwise... but that usually means "no initialization". So list it in the initializer-list of the constructor. Or use C++11's new *brace-or-equal-initializer* syntax, exemplified by the second part of Vlad's answer. – Ben Voigt Oct 10 '14 at 13:37
  • @BenVoigt yes the "no initialization" is my problem, and I mixed something up, here is what I read in your example: http://ideone.com/6zK1lK – Wolf Oct 10 '14 at 13:41
  • @Wolf: From my answer: "A member works, but then you need to qualify all access. So inheritance is better" Your code is the same but using a member, so yes it works. – Ben Voigt Oct 10 '14 at 13:47
  • @BenVoigt ..., only now I see what you meant by `you need to qualify all access` sorry for the confusion. – Wolf Oct 10 '14 at 14:01
  • 3
    A warning: In C++98, if `MoneyData` also contains a non-POD member (e.g. `std::string`) then suddenly the `int` and `double` members are not zero-initialized any more. – M.M Dec 08 '14 at 02:30
  • @MattMcNabb: By construction it doesn't contain any non-POD member. Those stay in the main `Money` object. – Ben Voigt Dec 08 '14 at 02:47
  • For cleanliness, shouldn't `MoneyData` have a protected non-virtual destructor (http://stackoverflow.com/a/7404130) ? – Nemo Jun 02 '15 at 18:39
  • @Nemo: Nah, the value of doing that is so low, I'd rather keep the type POD (standard-layout, trivial) – Ben Voigt Jun 02 '15 at 18:57
  • @BenVoight: Your `struct Money` is already non-POD because it has a non-trivial (user-provided) constructor, and my suggestion preserves the standard-layout-ness. – Nemo Jun 02 '15 at 23:54
  • @Nemo: `MoneyData` is currently POD, and your suggestion breaks that. – Ben Voigt Jun 03 '15 at 00:52
  • Well, I was assuming `MoneyData` here was an internal utility class, not intended to be used directly, since the whole point is to implement `struct Money`. I suppose it depends on what you are trying to do... The dangers of slicing (every time anybody uses this gadget, forever) far outweigh the (one-time) cost of just spelling out the field-by-field initialization, IMO. So I would either just spell it all out as in the question, or I would forbid the slicing with a protected destructor in your version. Matter of taste, I suppose. – Nemo Jun 03 '15 at 01:00
  • @M.M: you says that if `MoneyData` contains a non-POD member (e.g. std::string) then suddenly the int and double members are not zero-initialized any more in C++98. But this seems to be wrong. See [this] (http://melpon.org/wandbox/permlink/k1R9skzrkByS5GwK) & [this] (http://melpon.org/wandbox/permlink/LETztwxNAsLSoZBl) & [this] (http://cpp.sh/8huay) code tested in clang & g++. It gives 0 both in C++98 & C++03? – Destructor Mar 10 '16 at 18:42
  • @Destructor: You aren't testing with a C++98 compiler, but with a C++17 compiler in C++98 mode. Compiler writers usually use the latest rule in all modes, if possible. And it is possible, because the older Standard didn't specify the outcome, so "zeroed" is just as legal a value for an uninitialized variable as any other. – Ben Voigt Mar 10 '16 at 19:02
  • @BenVoigt: can you tell me the relevant citation from **C++98** standard that says that If class contain non pod member like `std::string` then other data member which are of type like `int` don't get zero initialized when using `Test t=Test();` ? – Destructor Mar 10 '16 at 19:04
  • @Destructor: I only have text for C++03. Sorry. In C++03 the rules make this work even if there is a non-POD class member in `MoneyData`. – Ben Voigt Mar 10 '16 at 19:16
  • @Destructor: Please read http://stackoverflow.com/a/1613578/103167 It was different in C++98. Not because there's any rule that says members don't get zero-initialized, but because the rule that does make them zero-initialized only triggers when the data type is POD. – Ben Voigt Mar 10 '16 at 19:19
  • 3
    @Destructor In C++98 your code causes undefined behaviour by reading an uninitialized variable, so any output at all would be compliant with the standard. The relevant clauses are 8.5/7 "An object whose initializer is `()`, shall be default-initialized.", 8.5/5 defines *default-initialized* "If `T` is a non-POD class type, the default constructor for `T` is called ", and 12.1/7 says that implicitly-declared default constructor behaves as if it had an empty function body. 12.6/4 says that nonstatic data members of non-class type, without an initializer, are not initialized by the constructor. – M.M Mar 10 '16 at 20:09
  • 3
    In C++03 it was changed so that `()` recursively value-initializes an aggregate whether or not it is POD – M.M Mar 10 '16 at 20:12
2

You can use the default constructor the following way

Money m = {};

Or you can set initialization of data members inside the class definition:

struct Money
{
    double amountP = 0.0, amountG = 0.0, totalChange = 0.0;
    int twenty = 0, ten = 0, five - 0, one = 0, change = 0;
    int quarter = 0, dime = 0, nickel = 0, penny = 0;

    void foo();
    Money()  {}
};
Vlad from Moscow
  • 224,104
  • 15
  • 141
  • 268
  • 1
    I don't want to rely on the caller doing `= {};`, I'd rather my object work properly even if they write `Money m; m.foo();`. The second option is good but only available in C++11, it would be great if there is a C++98 solution also. – M.M Apr 09 '14 at 22:46
  • 2
    Your first solution doesn't work because the OP has a NOP default constructor defined. He needs to either get rid of that, or `default` it instead of the empty body. – Praetorian Apr 09 '14 at 22:48
  • 2
    @VladfromMoscow, Since there is a user-provided constructor, it is not an aggregate. Thus, the list-initialization value-initializes the object. Since there is a user-provided default constructor, it is default-initialized as a result. Initializers are 8.5, aggregates are 8.5.1 and list-initialization is 8.5.4. – chris Apr 09 '14 at 23:03
  • @chris From the C++ Standard: "— Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized." – Vlad from Moscow Apr 09 '14 at 23:06
  • 1
    @VladfromMoscow, Go to 8.5 to the first point of value-initialization. *To value-initialize an object of type T means: — if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or __a default constructor that is user-provided__ or deleted, then the object is default-initialized;* – chris Apr 09 '14 at 23:06
  • @chris I thought that using empty braces zero initializes an object with default constructor. – Vlad from Moscow Apr 09 '14 at 23:15
  • @VladfromMoscow, Not as far as I can tell from following along in the standard, my process being what was stated above. – chris Apr 09 '14 at 23:30
  • 1
    @VladfromMoscow: An object with a user-declared constructor cannot be zero initialized, since that might break invariants that the constructor is supposed to establish. (It might still be pre-initialized to zero if it has static storage duration, but the object's lifetime will not begin until the user constructor has finished execution. There's no way to bypass the user constructor and initialize to zero instead.) – Ben Voigt Oct 10 '14 at 13:24