0

Similar to: How can I initialize base class member variables in derived class constructor?, but I was wondering why if I have:

class A {
public:
    A(int val);
    virtual int get_val() = 0;
protected:
    int val;
};

class B : public A {
public:
    B(int val, int extra);
    int get_val() = 0;
private:
    int extra;
};

I'm wondering what is the difference between doing this:

A::A(int val) : val(val) {}

and:

A::A(int val) {val = val;}

And also why, when I'm in the constructor for class B, I can't do:

B::B(int b, int extra) : A(b) {
    extra = extra;
}

But I can do:

B::B(int b, int extra) : A(b) {
    this->extra = extra;
}

or,

B::B(int b, int extra) : A(b), extra(extra){}

to store the value of extra in B. If I don't do those it's not stored. I'm very confused what's happening.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Athena
  • 320
  • 2
  • 12

2 Answers2

4

I'm wondering what is the difference between doing this:

A::A(int val) : val(val) {}

and:

A::A(int val) {val = val;}

The first is right and the second is wrong.

More precisely, the first one initialises the data member A::val with the value of the constructor parameter val, which is without any doubt precisely what you intend to do.

The second one, in contrast, does nothing with A::val, leaving it with an uninitialised value, which will later cause undefined behaviour as soon as you try to read from the data member. The val = val; line assigns the constructor parameter val to itself, which is completely meaningless.

The second one would be okayish if you used a different parameter name to disambiguate, for example:

A::A(int new_val) { val = new_val; }

It would also work fine if you did like in your own this->extra = extra; example:

A::A(int val) { this->val = val; }

In both cases, the compiler now knows that you mean A::val.

However, this form of constructor implementation is atypical in C++ and often a sign of a Java or C# programmer. It leaves A::val uninitialised for a moment and then assigns it a value. That just isn't very logical. Why not initialise it right away if you can? It also won't work with const members, with reference members or with data types that don't have default constructors or a way to assign something later.

By the way, this doesn't have anything to do with efficiency but only with correctness and readability.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
1

I'm wondering what is the difference between doing this:

A::A(int val) : val(val) {}

and:

A::A(int val) {val = val;}

No difference for primitive types. However, if val is a non-primitive type, e.g. std::string, Using the first way(initialization list) is more efficient. Think of

Method 1 is the same as:

string val(val);

Method 2 is the same as:

string val; //default initialize first;
val = val2; //assign val2 to val

That is, even if you do not provide anything in the initialization list, compiler will still call the default constructor to initialize it first before going into {} block. Since primitive types do not have constructors, using initialization list does not really improve the efficiency.

And also why, when I'm in the constructor for class B, I can't do:

B::B(int b, int extra) : A(b) {

extra = extra;

}

Similar to the explanation above. Inside the {}, "extra" will be defined, and local object overshadow the data member. However, there is no harm in choosing a different parameter name.

Community
  • 1
  • 1
gchen
  • 1,173
  • 1
  • 7
  • 12
  • 1
    *"No difference for primitive types."* - No, `val = val;` in that constructor is self-assignment and will later cause undefined behaviour when an attempt is made to read `A::val`. – Christian Hackl Apr 15 '18 at 16:08
  • Yeah, thanks for the correction! I was focusing on the difference between with and without the initialization list. And I also touched this point in the second part of my answer. – gchen Apr 15 '18 at 16:58