What's the differences between member initializer list and default member initializer on non-static data member?
Asked Answered
C

5

29

I'd like to understand what's the differences of using one form rather than the other (if any).

Code 1 (init directly on variables):

#include <iostream>

using namespace std;

class Test 
{
public:
    Test() {
        cout<< count;
    }

    ~Test();

private:
    int count=10;
};

int main()
{
    Test* test = new Test();
}

Code 2 (init with initialization list on constructor):

#include <iostream>

using namespace std;

class Test 
{
public:
    Test() : count(10) {
        cout<< count;
    }

    ~Test();

private:
    int count;
};

int main()
{
    Test* test = new Test();
}

Is there any difference in the semantics, or it is just syntactic?

Cosmopolitan answered 13/4, 2016 at 13:38 Comment(11)
Your first construction is illegal without C++11 extensions, and your first construction does not work in case count were const.Readiness
@Readiness good point, mind if I add that to my answer?Wheelbarrow
@OMGtechy. Of course not.Readiness
@Readiness the current standard for c++ is c++14. I think we can assume this to be the baseline unless the question is tagged c++03, no?Hinder
@RichardHodges yes but it's still a helpful commentWheelbarrow
@RichardHodges Do you ever run software on super computers? I had to break these kind of constructions out of our code, because it did not work on the IBM BlueGene compiler. I thought it was worth mentioning in a comment.Readiness
@Readiness "your first construction does not work in case count were const." Why?Consuetudinary
@Consuetudinary yup, that bit is just flat-out wrong.Hinder
That is indeed very wrong. My excuse. But if I delete it, the discussion below does not make sense anymore.Readiness
@Readiness fair enough. It seems a real shame that c++14 has not been made available on BlueGene. all those redundant moves and imperfect forwarding... :-( no wonder it can't win at chess ;-)Hinder
No c++14 would be one thing, but no c++11? The horror...Uncertain
U
23

Member initialization

In both cases we are talking about member initialization. Keep in mind that the members are initialized in the sequence in which they are declared in the class.

Code 2: Member initializer list

In the second version:

Test() : count(10) {

: count(10) is a constructor initializer (ctor-initializer) and count(10) is a member initializer as part of the member initializer list. I like to think of this as the 'real' or primary way that the initialization happens, but it does not determine the sequence of initialization.

Code 1: Default member initializer

In the first version:

private:
    int count=10;

count has a default member intitializer. It is the fallback option. It will be used as a member initializer if none is present in the constructor, but in the class the sequence of members for initialization is determined.

From section 12.6.2 Initializing bases and members, item 10 of the standard:

If a given non-static data member has both a brace-or-equal-initializer and a mem-initializer, the initialization specified by the mem-initializer is performed, and the non-static data member’s brace-or-equal-initializer is ignored. [ Example: Given

struct A {
int i = / some integer expression with side effects / ;
A(int arg) : i(arg) { }
// ...
};

the A(int) constructor will simply initialize i to the value of arg, and the side effects in i’s brace-or-equalinitializer will not take place. —end example ]

Something else to keep in mind would be that if you introduce a non-static data member initializer then a struct will no longer be considered an aggregate in C++11, but this has been updated for C++14.


Differences

what's the differences of using one form rather than the other (if any).

  • The difference is the priority given to the two options. A constructor initializer, directly specified, has precedence. In both cases we end up with a member initializer via different paths.
  • It is best to use the default member initializer because
    • then the compiler can use that information to generate the constructor's initializer list for you and it might be able to optimize.
    • You can see all the defaults in one place and in sequence.
    • It reduces duplication. You could then only put the exceptions in the manually specified member initializer list.
Uncertain answered 13/4, 2016 at 14:16 Comment(0)
E
13

In the C++ Core Guidelines (see note 1 below), Guideline C.48 recommends the first approach (in-class initializers.) The reasoning provided is:

Makes it explicit that the same value is expected to be used in all constructors. Avoids repetition. Avoids maintenance problems. It leads to the shortest and most efficient code.

In fact if your constructor does nothing but initialize member variables, as in your question, then Guideline C.45 is firmer still, saying to use in-class initializers for sure. It explains that

Using in-class member initializers lets the compiler generate the function for you. The compiler-generated function can be more efficient.

I am not going to argue with Stroustrup, Sutter, and several hundred of their friends and colleagues even if I haven't written a compiler so I can't prove it's more efficient. Use in-class initializers wherever you can.

  1. If you're not familiar with the guidelines do follow the links to see sample code and more explanations.
Exacerbate answered 13/4, 2016 at 14:21 Comment(0)
C
7

The difference I can think of is that member initializer list is prior to default member initializer.

Through a default member initializer, which is simply a brace or equals initializer included in the member declaration, which is used if the member is omitted in the member initializer list.

If a member has a default member initializer and also appears in the member initialization list in a constructor, the default member initializer is ignored.

For example:

class Test 
{
public:
    Test() {}  // count will be 10 since it's omitted in the member initializer list
    Test(int c) : count(c) {} // count's value will be c, the default member initializer is ignored. 
private:
    int count = 10;
};
Consuetudinary answered 13/4, 2016 at 13:59 Comment(0)
I
2

There is no difference in the code. The difference would come if you would be would have more than one constructor overload and in more than one count would be 10. With the first version you would have less writing to do.

class Test 
{
public:
    Test() = default;
    Test(int b) : b(b) {} // a = 1, c = 3

    ~Test();

private:
    int a = 1;
    int b = 2;
    int c = 3;
};

As opposed to the second version where the above code would look like this:

class Test 
{
public:
    Test() : a(1), b(2), c(3) {}
    Test(int b) : a(1), b(b), c(3) {}

    ~Test();

private:
    int a;
    int b;
    int c;
};

The difference gets bigger with more member variables.

Intemerate answered 13/4, 2016 at 13:50 Comment(6)
Do you mean to keep writing b(10) instead of using the int parameter you're accepting?Humph
@Humph Yes it was a typo. I fixed it.Intemerate
Why you init variables in the second version? There's already init list that will do it.Cosmopolitan
@paizza Copy / paste mistake ;) Thanks for noticing.Intemerate
Honestly I prefer the first version, instead of use init list (which make confusion).Cosmopolitan
Could you also possibly use default parameters with the constructor? If the user would pass arguments into the constructor, they would overwrite the default parameters.Embosser
W
1

When you initialise next to the declaration of the member, this is valid only in C++11 onwards, so if you're in C++98/03 you outright cannot do this.

If the value never changes, you could choose to make this a constexpr static instead and the compiler would then be required to not use any extra storage for the value (so long as you don't define it) and instant use constant propagation wherever the value is used instead.

One disadvantage of using the by-declaration syntax is that it must be in the header, which will result in a recompile of all translation units that include the header every time you want to change its value. If this takes a long time, that might be unacceptable.

Another difference is that using the member initialisation list lets you change the value for each constructor, whilst using the by-declaration version only allows you to specify one value for all constructors (although you could overwrite this value ... but I'd personally avoid this as it could get quite confusing!).


As an aside, there's no need to use new here to create an instance of Test. This is a common mistake when people come to the language from other languages and I wanted to make you aware. There are of course many uses for doing this outside of your example.

Wheelbarrow answered 13/4, 2016 at 13:43 Comment(3)
It is not true that by-delcaration syntax required that the values are constant. See hereIntemerate
@Intemerate that was a typo I thought I removed, are you referring to a previous version of the answer? :)Wheelbarrow
No. Your first lines imply that: "When using the member initialisation list syntax, you can initialise members based upon non-constants (for example, constructor parameters)."Intemerate

© 2022 - 2024 — McMap. All rights reserved.