1

I was going through some of the questions on constructor initialization list when I bumped into this.

Consider this:

class Student {
    public:
        Student() {
            id = 0;
        }
        Student(int i) {
            id = i;
        }
    private:
        int id;
};

Now, check this out:

By the time you get in the body of the constructor, all fields have already been constructed; if they have default constructors, those were already called. Now, if you assign a value to them in the body of the constructor, you are calling the copy constructor. That is inefficient, because two constructors end up being called instead of one.

Source: What does a colon following a C++ constructor name do?

So, does it mean that when I call the parameter-less constructor, the copy constructor is also being called?

Please explain. This is really confusing.

Particularly the meaning of the first line:

By the time you get in the body of the constructor, all fields have already been constructed

Community
  • 1
  • 1
  • _"So, does it mean that when I call the parameter-less constructor, the copy constructor is also being called?"_ No. Where does the cite state so? – πάντα ῥεῖ Feb 15 '15 at 18:19
  • 1
    This isn't a great example since `int` is a POD and thus isn't "default constructed". – Jim Buck Feb 15 '15 at 18:20
  • Please explain what was the intention there. When are two constructors being called instead of one. I'm really confused. –  Feb 15 '15 at 18:22
  • 1
    They are referring to the copy constructor of the members (if they were a class type and not an `int`). But this is actually a mistake because what's called is the assignment operator and not the copy constructor. – interjay Feb 15 '15 at 18:24
  • 1
    The main problem is that you quoted from an answer that isn't quite right. First, replace "all fields" with "all member variables". Second, "if you assign a value to them in the body of the constructor, you are calling the copy constructor" isn't true. You're actually calling the assignment operator after the default constructor has been called. There is no case where 2 constructors are called on the same object. It makes no sense unless one constructor explicitly forwards to another. – thang Feb 15 '15 at 18:26
  • By the way, you could save the code of your default constructor by using default a default values. You'd need only one ctor here : `Student(int i=0) : id(i) { }` – Christophe Feb 15 '15 at 19:12

2 Answers2

4

It means that int id has already been initialized before you get to the line id = 0. Because no explicit initializer has been specified, it has been default initialized. Additionally, because it is an int, the initialization rules tell use that it will have some indeterminate value.

In practice, for an int, or any type that is extremely cheap to initialize, this doesn't matter too much.

If instead of using an int member, we used a class, we could more clearly see what is actually going on behind the scenes:

#include <iostream>

class Verbose {
    public:
        Verbose() {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }

        Verbose(int) {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }

        Verbose(Verbose const &) {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }

        Verbose & operator=(Verbose const &) {
            std::cout << __PRETTY_FUNCTION__ << "\n";
            return *this;
        }

        ~Verbose() {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }
};

class Object {
    public:
        Verbose v;

        Object() {
            v = Verbose(3);
        }
};

int main() {
    Object o;
}

This code will output:

Verbose::Verbose()
Verbose::Verbose(int)
Verbose &Verbose::operator=(const Verbose &)
Verbose::~Verbose()
Verbose::~Verbose()

Note that we:

  1. We use the default constructor to create v.
  2. We create a temporary Verbose(3)
  3. We then use the assignment operator to assign the temporary to the member variable.
  4. We then destroy the temporary.
  5. When Object o goes out of scope, we then destroy the member variable.

Note that we basically constructed Verbose v twice! We first used the default constructor, and then we basically rebuilt it using the operator= call. If we used an initializer list, we could reduce that down to one call to Verbose(int).

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • This is a bad example in the OP since `int id` is not already constructed due to it being a POD type. – Jim Buck Feb 15 '15 at 18:23
  • Yes, when we declare an instance of a class, some space in memory is reserved for the member variables and it will either have garbage value or some specified value during declaration. –  Feb 15 '15 at 18:24
  • In the stated code, it has not been initialized until the assignment statement. – Jim Buck Feb 15 '15 at 18:25
  • @JimBuck: It has been default initialized! – Bill Lynch Feb 15 '15 at 18:25
  • Ints do not get default initialized. POD types do not get default initialized. – Jim Buck Feb 15 '15 at 18:26
  • @JimBuck: The result of default initializing a POD type is that it has an indeterminate value. That does not mean that default initialization hasn't happened. cppreference has some thoughts on this: _Only (possibly cv-qualified) non-POD class types (or arrays thereof) with automatic storage duration were considered to be default-initialized when no initializer is used. Scalars and POD types with dynamic storage duration were considered to be not initialized (since C++11, this situation was reclassified as a form of default initialization_ – Bill Lynch Feb 15 '15 at 18:26
  • Hmm, I have a different definition of default initialization then you do then, but any sane compiler would not touch the memory that `id` lives in unless the programmer explicitly has an assignment statement for it. – Jim Buck Feb 15 '15 at 18:27
  • 2
    @JimBuck: You have a different definition of "default initialization" than the official C++ Standard. – Ben Voigt Feb 15 '15 at 18:29
  • "not initialized" is "reclassified as a form of default initialization"? That's quite a bizarre way of stating it, but in any case, that twist of semantics on the part of C++11 is not relevant to the OP's curiosity. – Jim Buck Feb 15 '15 at 18:29
  • 1
    Well, the rule that (slight paraphrase) "To default-initialize an object of non-class non-array type means no initialization is performed" is present in C++03 as well. What Bill quoted has to do with dynamic allocation, so it doesn't apply in any way. – Ben Voigt Feb 15 '15 at 18:31
  • Also, you proved that the copy constructor isn't called at all. Why does your answer claim that it is? – Ben Voigt Feb 15 '15 at 18:35
  • @BenVoigt: Thats a good point on the first paragraph. And I'll fix the second note as well. At one point, I was using a operator=() overload instead of the copy constructor. – Bill Lynch Feb 15 '15 at 18:37
  • @JimBuck: The historical oddity that is "variables, if not initialized at declaration, may be 'initialized' on first assignment" is swept aside in C++ in favour of more sensible semantics. In short, "initialization" here does not mean what _you_ think it means. – Lightness Races in Orbit Feb 15 '15 at 18:48
  • Yes, it apparently means "does nothing". :) But the twist in semantics makes it hard for a newbie to understand what's happening under the hood and whether his code will be performant or not. Even some of the answers, before they were changed, were confused and saying that `id` would be default-constructed to 0, which is 100% false. I blame the semantics for that confusion. – Jim Buck Feb 15 '15 at 19:34
3

They mean this

Student() {
    id = 0;
}

Is less efficient than

Student() : id(0) {}

In this particular example, id would be initialized in a single step since the member is just an int.

In contrast, the latter method would make a difference if Student had more complex members, like perhaps Backpack and Books. If these classes weren't POD, the first method would take two steps to initialize the member, and the second would only take one step to initialize.

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
  • 1
    Well in this particular case, it doesn't matter because int is a primitive. – thang Feb 15 '15 at 18:20
  • Yup, thang nailed it. `int` does not automatically get a 0. – Jim Buck Feb 15 '15 at 18:21
  • Yes this example wasn't the best to illustrate the issue. I added a sentence at the end to try to address this. – Cory Kramer Feb 15 '15 at 18:23
  • If you are going to edit, you should remove the "In the first, ..." sentence since it's not true. – Jim Buck Feb 15 '15 at 18:26
  • @JimBuck: It is correct, `id` actually is default initialized in the first case. Default initialization of a primitive is a no-op. – Ben Voigt Feb 15 '15 at 18:27
  • @Cyber: I've been taught that whenever we declare an instance of a class (`Eg: Student s1;`), some memory is reserved for the member variables, containing garbage values until we initialize the variables. Now, the sole purpose of the constructor is to initialize those variables CORRECTLY. So, in the first version of the constructor you have mentioned, the garbage value is replaced by '0'. I don't understand how the second version is different to it. –  Feb 15 '15 at 18:29
  • Like I said in another comment, if a "no-op" is classified as a "default initialization", that's just a bizarre twist of semantics and has nothing to do with the OP's real curiosity. – Jim Buck Feb 15 '15 at 18:30
  • 1
    @TheVigilante: In the second version, the garbage value never existed. – Bill Lynch Feb 15 '15 at 18:32
  • 1
    I agree with @JimBuck's sentiment (despite the confusing terminology): assigning 0, with nothing happening before that assignment (since default initialization of an int does nothing), is one step, not two. I'd go one step further and say that if your compiler makes `Student() { id = 0; }` less efficient than `Student() : id(0) { }`, you've really got an exceptionally poor compiler. You need some class type with a non-trivial constructor for default initialization to actually do anything. –  Feb 15 '15 at 18:43