C++11 Member Initializer List VS In-Class Initializer

C++11 member initializer list vs in-class initializer?

No, these are not the same.

The difference between them is the same that applies for direct-initialization vs. copy-initialization, which is subtle but often very confusing.

§12.6.2 [class.base.init]:


  1. The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of 8.5 for direct-initialization. [...]

  2. In a non-delegating constructor, if a given non-static data member or base class is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4), then

    — if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5;

§8.5 [dcl.init]:


  1. The initialization that occurs in the form

    T x = a;


as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.

Initializing a non-static data member on a member-initializer-list follows the rules of direct-initialization, which doesn't create intermediate temporaries that need to be moved/copied (if compiled without a copy-elision), neither the type of the data member must be copyable/movable (even if the copy is elided). In addition, a direct-initialization introduces an explicit context, while a copy-initialization is non-explicit (if a constructor selected for the initialization is explicit, the program won't compile).

In other words, the obj s = obj("value"); syntax won't compile if obj is declared as:

struct obj
{
obj(std::string) {}
obj(const obj&) = delete;
};

or:

struct obj
{
obj(std::string) {}
explicit obj(const obj&) {}
};

As a more tangible example, while the below won't compile:

struct any
{
std::atomic<int> a = std::atomic<int>(1); // ill-formed: non-copyable/non-movable
std::atomic<int> b = 2; // ill-formed: explicit constructor selected
};

this one will:

struct any
{
std::atomic<int> a;
std::atomic<int> b{ 2 };
any() : a(1) {}
};

Which way is better (performance) ?

With a copy-elision enabled both have identical performance. With copy-elision disabled, there is an additional copy/move constructor call upon every instantiation when the copy-initialization syntax is used (that obj s = obj("value"); is one of).


Is there another way ?

The brace-or-equal-initializer syntax allows one to perform a direct-list-initialization as well:

class any {
public:
obj s{ "value" };
any() {}
};

Are there any other differences?

Some other differences that are worth mentioning are:

  1. Brace-or-equal-initializer must reside in a header file along with a class declaration.
  2. If both are combined, member-initializer-list takes priority over brace-or-equal-initializer (that is, brace-or-equal-initializer is ignored).
  3. (C++11 only, until C++14) A class that uses brace-or-equal-initializer violates constraints for an aggregate type.
  4. With the brace-or-equal-initializer syntax it's not possible to perform a direct-initialization other than a direct-list-initialization.

What exactly is the in-class-initializer?

Here is a simple example for in-class initialization. It's useful for less typing, especially when more than one constructor signatures are available. It's recommend in the core guidelines, too.

class Foo {
public:
Foo() = default; // No need to initialize data members in the initializer list.
Foo(bool) { /* Do stuff here. */ } // Again, data member already have values.

private:
int bar = 42;
// ^^^^ in-class initialization
int baz{};
// ^^ same, but requests zero initialization
};

As the data members are explicitly initialized, the second part of your questions doesn't really apply to to in-class initialization.

Constructor initializer list vs initializing in the header file

This:

MyClass::MyClass()
{
variable1 = 10;
boolean2 = false;
};

is not initialization! The members will be initialized before the body of the constructor runs and then you are assigning values. What you mean is probably the difference between

Initializer list

MyClass::MyClass() : variable1(10), boolean2(false) {}

and in class initialization (available since C++11 I believe) in the header:

struct MyClass {
int variable1 = 10;
boolean2 = false;
};

In both the last cases, the values are used to initialize the members, so there is no difference in speed. However, in the first case you are doing more than you actually want to (initialization + assignment) and you should avoid it if possible.

The subtle difference between in-class initialization and initializer list (see eg here) is that

variable1 = 10; 

may involve a copy. This can be circumvented by using direct-list-initialization:

 struct MyClass {
int variable1{10};
bool boolean2{false};
};

However, for an int and a bool this wont make any difference whatsoever.

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

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.

Order of In Class Initialization versus Constructor Initialization List

This is guaranteed by the standard that non-static data members will be initialized in the order of their declarations in the class definition. How they're initialized (via default member initializer or member initializer list) and the order of these initializers don't matter.

[class.base.init]#13.3

(13.3) - Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. — end note ]

That means, the initialization order will always be ptr -> m1 -> m2.

Initializer list vs Constructor assignment vs variable defining

This excerpt has been taken from "Inside the C++ Object Model" by Stanley B. Lippman.

You must use the member initialization list in the following cases in
order for your program to compile:

1. When initializing a reference member

2. When initializing a const member

3. When invoking a base or member class constructor with a set of arguments

4. A few efficiency cases. (Here the program is correct w/o member initialization list)

For points 1-3, member initialization list is a must.

For point 4, it is not compulsory.

For example(point 4), given :

class Word {
String _name;
int _cnt;
public:
// not wrong, just naive ...
Word() {
_name = 0;
_cnt = 0;
}
};

This implementation of the Word constructor initializes _name once, then overrides the initialization with an assignment, resulting in the creation and the destruction of a temporary String object.

A significantly more efficient implementation would have been coded:

// preferred implementation
Word::Word : _name( 0 )
{
_cnt = 0;
}

Due to this optimisation, a lot of people prefer member initialization list, as a default approach to write constructors.

// some insist on this coding style
Word::Word()
: _cnt( 0 ), _name( 0 )
{}

A reasonable question to ask at this point is, what actually happens to the member initialization list?

The compiler iterates over the initialization list, inserting the initializations in the proper order within the constructor prior to any explicit user code.

For example, the previous Word constructor is expanded as follows:

// Pseudo C++ Code
Word::Word( /* this pointer goes here */ )
{
_name.String::String( 0 );
_cnt = 0;
}

Note : The order in which the list entries are set down is determined
by the declaration order of the members within the class declaration,
not the order within the initialization list. In this case, _name is
declared before _cnt in Word and so is placed first.

So coming back to your question :

class B is fine(since you are using primitive datatypes).

class A will generate the same code as class B

As for class C, the default constructor is first called, and then initialization of width and height would happen. This method should be preferred when there are going to be more than 1 constructor, and for each constructor width and height need to be defaulted to your desired values.

However, since the advent of C++11, and use of {} as uniform initialization, a more recommended approach for writing class C would be :

class C
{
public:
int width {500};
int height {300};
};

Has the new C++11 member initialization feature at declaration made initialization lists obsolete?

No, they are not obsolete as this article Get to Know the New C++11 Initialization Forms says in the Class Member Initialization section (emphasis mine):

Bear in mind that if the same data member has both a class member initializer and a mem-init in the constructor, the latter takes precedence. In fact, you can take advantage of this behavior by specifying a default value for a member in the form of a class member initializer that will be used if the constructor doesn't have an explicit mem-init for that member. Otherwise, the constructor's mem-init will take effect, overriding the class member initializer. This technique is useful in classes that have multiple constructors

So although in class member initialization is a nice convenience it does not remove the need for initialization lists but both features instead work together to give you a nice way to specify default values and override them when needed. This seems to be also how Bjarne Stroustrup sees it too, he says:

This saves a bit of typing, but the real benefits come in classes with multiple constructors. Often, all constructors use a common initializer for a member:

and provides an example of members which have a common initializer:

class A {
public:
A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {}
A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {}
A(D d) : a(7), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {}
int a, b;
private:
HashingFunction hash_algorithm; // Cryptographic hash to be applied to all A instances
std::string s; // String indicating state in object lifecycle
};

and says:

The fact that hash_algorithm and s each has a single default is lost in the mess of code and could easily become a problem during maintenance. Instead, we can factor out the initialization of the data members:

class A {
public:
A(): a(7), b(5) {}
A(int a_val) : a(a_val), b(5) {}
A(D d) : a(7), b(g(d)) {}
int a, b;
private:
HashingFunction hash_algorithm{"MD5"}; // Cryptographic hash to be applied to all A instances
std::string s{"Constructor run"}; // String indicating state in object lifecycle
};

Note: disadvantage in C++11

There is one disadvantage to using in class member initialization in C++11 since it makes a class a non-aggregate we can no longer use aggregate initialization which may be rather surprising. This is not the case in C++14 where this restriction was removed. See: C++11 aggregate initialization for classes with non-static member initializers for more details.

C++ - Mixing default member initializers and member initialization lists - bad idea?

The presence of default member initializers changes nothing about the order in which subobjects of a type are initialized. It will always be in declaration order.

Style is up to you. The more constructors you have, the more you gain by using DMIs, since you're not repeating initialization that doesn't change. At the same time, if you start making constructors that override DMIs, that can create confusion about what the object's initial state is. The key is to try not to be surprising about what's going on.

In your particular case however, I'd say that you have too many variables. Your array should just be a std::vector<uint8_t>. Having the header_ pointer is dubious but defensible (though it's initialized incorrectly; you need to use placement-new to satisfy C++'s object model). But data_length_ can be computed on an as-needed basis.

The fewer members you have, the less chance for confusion about how they get initialized.

Which part of standard determines priority of in-place member vs initializer list?

The value provided in your constructor is explicitly preferred.

[class.base.init]/10: If a given non-static data member has both a default member initializer and a mem-initializer, the initialization specified by the mem-initializer is performed, and the non-static data member's default member initializer is ignored.



Related Topics



Leave a reply



Submit