How do class members get initialized if I don't do it explicitly?
Asked Answered
S

8

206

Suppose I have a class with private memebers ptr, name, pname, rname, crname and age. What happens if I don't initialize them myself? Here is an example:

class Example {
    private:
        int *ptr;
        string name;
        string *pname;
        string &rname;
        const string &crname;
        int age;

    public:
        Example() {}
};

And then I do:

int main() {
    Example ex;
}

How are the members initialized in ex? What happens with pointers? Do string and int get 0-intialized with default constructors string() and int()? What about the reference member? Also what about const references?

I'd like to learn it so I can write better (bug free) programs. Any feedback would help!

Swoosh answered 27/6, 2010 at 13:21 Comment(5)
For book recommendations, see #388742Spirituel
Mike, ow, I mean chapter from some book that explains it. Not whole book! :)Swoosh
It probably would be a good idea to read a whole book on a language you intend to program in, though. And if you already read one and it didn't explain this, then it wasn't a very good book.Placebo
Scott Meyers (a popular ex-pro C++ advice guru) states in Effective C++, "the rules are complicated--too complicated to be worth memorizing, in my opinion.... make sure that all constructors initialize everything in the object." So in his opinion, the easiest way to (attempt to) write "bug free" code is not to try to memorize the rules (and in fact he does not lay them out in the book), but to explicitly initialize everything. Note, however, that even if you take this approach in your own code, you might work on projects written by people who don't, so the rules may still be valuable.Docia
@TylerMcHenry What books on C++ do you consider "good"? I've read several books on C++, but none of them have explained this completely. As noted in my previous comment, Scott Meyers explicitly declines to provide the complete rules in Effective C++. I've also read Meyers' Effective Modern C++, Dewhurst's C++ Common Knowledge, and Stroustrup's A Tour of C++. To my memory, none of them explained the complete rules. Obviously I could have read the standard, but I'd hardly consider that a "good book"! :D And I expect Stroustrup probably explains it in The C++ Programming Language.Docia
P
268

In lieu of explicit initialization, initialization of members in classes works identically to initialization of local variables in functions.

For objects, their default constructor is called. For example, for std::string, the default constructor sets it to an empty string. If the object's class does not have a default constructor, it will be a compile error if you do not explicitly initialize it.

For primitive types (pointers, ints, etc), they are not initialized -- they contain whatever arbitrary junk happened to be at that memory location previously.

For references (e.g. std::string&), it is illegal not to initialize them, and your compiler will complain and refuse to compile such code. References must always be initialized.

So, in your specific case, if they are not explicitly initialized:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk
Placebo answered 27/6, 2010 at 13:29 Comment(10)
+1. It is worth noting that, by the strict standard definition, instances of primitive types along with a variety of other things (any region of storage) are all considered objects.Sigfrid
True, but if the OP had read the standard, he wouldn't be asking the question, so I don't think it's confusing to use object in the general object-oriented programming sense of something of class type, as opposed to primitive type. :)Placebo
"If the object's class does not have a default constructor, it will be a compile error if you do not explicitly initialize it" that's wrong! If a class does not have a default constructor, it is given a default default constructor which is empty.Slipover
@wiz I think he literally meant 'if the object does not have a default constructor' as in no generated one either, which would be the case if the class explicitly defines any constructors other than the default (no default ctor will be generated). If we get too pedanatic, we'll probably confuse more than help and Tyler makes a good point about this in his response to me before.Sigfrid
@stinky: it's not pendantry in this case: consider this simple code: class foo { public: int i; }; class bar { public: foo f; }; we don't have any costructor, but you can still instantiate bar and use it!Slipover
@Sigfrid That's what I meant, but wiz-loz's comment is worthwhile as well. A class that has some user-defined constructor but does not also have a user-defined default constructor will not have a default constructor at all. So failing to initialize an object of such a class would cause a compile error.Placebo
@wiz-loz I would say that foo does have a constructor, it's just implicit. But that's really an argument of semantics.Placebo
I interpret "default constructor" as a constructor which can be called without arguments. This would be either one you define yourself or implicitly generated by compiler. So the lack of it, means neither defined by yourself nor generated. Or that's how I see it.Gavingavini
"But that's really an argument of semantics." Well, sure, but semantics are important when discussing terminology!Docia
"they contain whatever arbitrary junk happened to be at that memory location previously" This is a misconception and/or implementation detail. The value is unspecified. It need not come from any "memory location". (Real-world example)Almucantar
M
34

First, let me explain what a mem-initializer-list is. A mem-initializer-list is a comma-separated list of mem-initializers, where each mem-initializer is a member name followed by (, followed by an expression-list, followed by a ). The expression-list is how the member is constructed. For example, in

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

the mem-initializer-list of the user-supplied, no-arguments constructor is name(s_str, s_str + 8), rname(name), crname(name), age(-4). This mem-initializer-list means that the name member is initialized by the std::string constructor that takes two input iterators, the rname member is initialized with a reference to name, the crname member is initialized with a const-reference to name, and the age member is initialized with the value -4.

Each constructor has its own mem-initializer-list, and members can only be initialized in a prescribed order (basically the order in which the members are declared in the class). Thus, the members of Example can only be initialized in the order: ptr, name, pname, rname, crname, and age.

When you do not specify a mem-initializer of a member, the C++ standard says:

If the entity is a nonstatic data member ... of class type ..., the entity is default-initialized (8.5). ... Otherwise, the entity is not initialized.

Here, because name is a nonstatic data member of class type, it is default-initialized if no initializer for name was specified in the mem-initializer-list. All other members of Example do not have class type, so they are not initialized.

When the standard says that they are not initialized, this means that they can have any value. Thus, because the above code did not initialize pname, it could be anything.

Note that you still have to follow other rules, such as the rule that references must always be initialized. It is a compiler error to not initialize references.

Moleskins answered 27/6, 2010 at 14:7 Comment(1)
This is the best way to initialize members when you want to strictly separate declaration (in .h) and definition (in .cpp) without showing too much internals.Reece
U
12

You can also initialize data members at the point you declare them:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

I use this form pretty much exclusively, although I have read some people consider it 'bad form', perhaps because it was only recently introduced - I think in C++11. To me it is more logical.

Another useful facet to the new rules is how to initialize data-members that are themselves classes. For instance suppose that CDynamicString is a class that encapsulates string handling. It has a constructor that allows you specify its initial value CDynamicString(wchat_t* pstrInitialString). You might very well use this class as a data member inside another class - say a class that encapsulates a windows registry value which in this case stores a postal address. To 'hard code' the registry key name to which this writes you use braces:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

Note the second string class which holds the actual postal address does not have an initializer so its default constructor will be called on creation - perhaps automatically setting it to a blank string.

Unicellular answered 29/10, 2014 at 15:41 Comment(0)
T
11

If you example class is instantiated on the stack, the contents of uninitialized scalar members is random and undefined.

For a global instance, uninitialized scalar members will be zeroed.

For members which are themselves instances of classes, their default constructors will be called, so your string object will get initialized.

  • int *ptr; //uninitialized pointer (or zeroed if global)
  • string name; //constructor called, initialized with empty string
  • string *pname; //uninitialized pointer (or zeroed if global)
  • string &rname; //compilation error if you fail to initialize this
  • const string &crname; //compilation error if you fail to initialize this
  • int age; //scalar value, uninitialized and random (or zeroed if global)
Tyrontyrone answered 27/6, 2010 at 13:24 Comment(5)
I experimented and it seems that string name is empty after initializing the class on the stack. Are you absolutely sure about your answer?Swoosh
string will have a constructor that provided an empty string by default - I'll clarify my answerTyrontyrone
@Swoosh : Paul is correct, but if you care about this behavior, it never hurts to be explicit. Throw it in the initializer list.Shulins
@bod: string is not a scalar type.Forename
It's not random! Random is too big word for that! If scalar members would be random, we wouldn't need any other random number generators. Imagine a program that analyzes data "left-overs" - like undelete files in memory - the data is far from random. It's not even undefined! It is usually hard to define, because usually we don't know what our machine does. If that "random data" that you just undeleted is the only image of your father, your mother may even find it offensive if you say that its random...Anorthite
T
8

It depends on how the class is constructed

Answering this question comes understanding a huge switch case statement in the C++ language standard, and one which is hard for mere mortals to get intuition about.

As a simple example of how difficult thing are:

main.cpp

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

In default initialization you would start from: https://en.cppreference.com/w/cpp/language/default_initialization we go to the part "The effects of default initialization are" and start the case statement:

  • "if T is a non-POD": no (the definition of POD is in itself a huge switch statement)
  • "if T is an array type": no
  • "otherwise, nothing is done": therefore it is left with an undefined value

Then, if someone decides to value initialize we go to https://en.cppreference.com/w/cpp/language/value_initialization "The effects of value initialization are" and start the case statement:

  • "if T is a class type with no default constructor or with a user-provided or deleted default constructor": not the case. You will now spend 20 minutes Googling those terms:
    • we have an implicitly defined default constructor (in particular because no other constructor was defined)
    • it is not user-provided (implicitly defined)
    • it is not deleted (= delete)
  • "if T is a class type with a default constructor that is neither user-provided nor deleted": yes

This is why I strongly recommend that you just never rely on "implicit" zero initialization. Unless there are strong performance reasons, explicitly initialize everything, either on the constructor if you defined one, or using aggregate initialization. Otherwise you make things very very risky for future developers.

Therese answered 18/3, 2020 at 19:9 Comment(0)
S
5

Uninitialized non-static members will contain random data. Actually, they will just have the value of the memory location they are assigned to.

Of course for object parameters (like string) the object's constructor could do a default initialization.

In your example:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value
Slipover answered 27/6, 2010 at 13:28 Comment(0)
F
2

Members with a constructor will have their default constructor called for initialisation.

You cannot depend on the contents of the other types.

Flamboyant answered 27/6, 2010 at 13:28 Comment(0)
M
0

If it is on the stack, the contents of uninitialized members that don't have their own constructor will be random and undefined. Even if it is global, it would be a bad idea to rely on them being zeroed out. Whether it is on the stack or not, if a member has its own constructor, that will get called to initialize it.

So, if you have string* pname, the pointer will contain random junk. but for string name, the default constructor for string will be called, giving you an empty string. For your reference type variables, I'm not sure, but it'll probably be a reference to some random chunk of memory.

Mckeon answered 27/6, 2010 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.