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

C++C++11Initialization

C++ Problem Overview


What difference between these ways of initializing object member variables in C++11 ? Is there another way ? which way is better (performance) ?:

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

Or

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

Thanks.

C++ Solutions


Solution 1 - C++

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]:

> 7. 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. [...]

> 8. 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]:

> 15. 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.

Solution 2 - C++

Both examples are equivalent.
Though only if the type is copyable or movable (check it for yourself) and NRVO is actually done (any halfway decent compiler will do it as a matter of course).

Though if you had many constructors and constructor-chaining were inappropriate, the first method would allow you not to repeat yourself.

Also, you can use that method to define aggregates with defaults different from aggregate-initialization for (some) members since C++14.

Solution 3 - C++

They are the same.

Neither is better than the other in terms of performance, and there is no other way to initialise them.

The benefit of in-class initialisation (the first in your example) is that the order of initialisation is implicit. In initialiser list you have to explicitly state the order - and compilers will warn of out-of-order initialisation if you get the ordering incorrect.

From the standard:

12.6.2.5
nonstatic data members shall be initialized in the order they were declared 
in the class definition

If you get the order wrong in your list, GCC will complain:

main.cpp: In constructor 'C::C()':
main.cpp:51:9: warning: 'C::b' will be initialized after
main.cpp:51:6: warning:   'int C::a'

The benefit of initialiser lists is perhaps a matter of taste - the list is explicit, typically in the source file. In-class is implicit (arguably), and is typically in the header file.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAhmed T. AliView Question on Stackoverflow
Solution 1 - C++Piotr SkotnickiView Answer on Stackoverflow
Solution 2 - C++DeduplicatorView Answer on Stackoverflow
Solution 3 - C++Steve LorimerView Answer on Stackoverflow