Has the New C++11 Member Initialization Feature at Declaration Made Initialization Lists Obsolete

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

Why is second initialization allowed in C++11

So that an individual constructor may override it.

From the original feature proposal:

It may happen that a data member will usually have a particular value, but a few specialized constructors will need to be cognizant of that value. If a constructor initializes a particular member explicitly, the constructor initialization overrides the member initializations as shown below: [..]

Remember, you can have more than one constructor.

Why use member init lists if there is default member initialization

There is no "must" here, a decision would probably be made based on circumstances.

For example, if the initialized value is not a constant, or if the value must be different in each of multiple constructors it makes sense to use the initializer list.

If you have to support pre-C++11 (and this may be more common than you think on large projects) you must always use the initializer list. Corresondingly if most of your code maintainers are not familiar with C++11 features it may be better to use the longstanding mechanism rather than the inline initialization.

If using C++11 is not an issue and you're initializing to a single constant value across multiple constructors, than the initializer form probably makes more sense.

C++ 11 standards - initialize member variables in header

It is not required that you use

QDialog* m_Dialog = Q_NULLPTR;

to initialize the member variable.

The above syntactic form is useful when there are many constructors in which you'll want to initialize the member variable with the same value. It reduces duplicate code.

If your class has the only constructor that you posted, you could leave the member variable declaration as

QDialog* m_Dialog;

without adversely affecting your program.

Why could const member be initialized twice?

It's not initialized twice; the default member initializer is just ignored. So for A a(555);, a.k is initialized as 555.

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.

From the standard, [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. [ 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 default member initializer will not take
place. — end example ]

On the other hand, given

class A {
public:
A() {} // k will be initialized via default member initializer, i.e. 666
A(int b) :k(b) {} // k will be initialized via member initializer list, i.e. b

const int k = 666;
};

then for A a;, a.k will be initialized as 666.

Why does C++11 not support designated initializer lists as C99?

C++ has constructors. If it makes sense to initialize just one member then that can be expressed in the program by implementing an appropriate constructor. This is the sort of abstraction C++ promotes.

On the other hand the designated initializers feature is more about exposing and making members easy to access directly in client code. This leads to things like having a person of age 18 (years?) but with height and weight of zero.


In other words, designated initializers support a programming style where internals are exposed, and the client is given flexibility to decide how they want to use the type.

C++ is more interested in putting the flexibility on the side of the designer of a type instead, so designers can make it easy to use a type correctly and difficult to use incorrectly. Putting the designer in control of how a type can be initialized is part of this: the designer determines constructors, in-class initializers, etc.

C++ Explicit declaration triggers a warning in the default constructor

The -Weffc++ warning are described as follows:

Warn about violations of the following style guidelines from Scott
Meyers' Effective C++ series of books:

  • Define a copy constructor and an assignment operator for classes with dynamically-allocated memory.
  • Prefer initialization to assignment in constructors.
  • Have operator= return a reference to *this.
  • Don't try to return a reference when you must return an object.
  • Distinguish between prefix and postfix forms of increment and decrement operators.
  • Never overload &&, ||, or ,.

The warning you are seeing is covered in Item 4: Make sure that objects are initialized before
they’re used
of Effective C++ 3rd edition which says (paraphrased):

The rules of C++ stipulate that data members of an object are
initialized before the body of a constructor is entered.

and:

A better way to write the [...] constructor is to use the member
initialization list instead of assignments [...] constructor yields
the same end result [...] but it will often be more efficient.

and (emphasis my wording):

The assignment-based version first called default constructors to
initialize the member variables then promptly assigned
new values on top of the default-constructed ones. All the work
performed in those default constructions was therefore wasted. The
member initialization list approach avoids that problem,

In C++11 in class member initializers (which also avoids this warning) can simplify initialization if most of your member variables have default values, the one disadvantage is that until C++14 this make your class a non-aggregate.



Related Topics



Leave a reply



Submit