C++ Initialization List Assignment Operator

Member Initialization List over Assignment

To start with, this article is not actually my own words. It is a small section of the fantastic book "Efective C++: 55 Specific Ways tom Improve Your Programs and Designs", by Scott Meyers, and I thought it warranted an article being made for it as it gives some very useful information.

Try not to get held up on the first few sentences as this is mid flow in the chapter...


For almost everything else, the responsibility for initialization falls on constructors. The rule there is simple: make sure that all constructors initialize everything in the object.

The rule is easy to follow, but it’s important not to confuse assignment with initialization. Consider a constructor for a class representing entries in an address book:



This will yield ABEntry objects with the values you expect, but it’s still not the best approach. The rules of C++ stipulate that data members of an object are initialized before the body of a constructor is entered. Inside the ABEntry constructor, theName, theAddress, and thePhones aren’t being initialized, they’re being assigned. Initialization took place earlier – when their default contructors were automatically called prior to entering the body of the ABEntry constructor. This isn’t true for numTimesConsulted, because it’s a built-in type. For it, there’s no guarantee it was initialized at all prior to its assignment.

A better way to write the ABEntry constructor is to use the member initialization list instead of assignments:



This constructor yields the same end result as the one above, but it will often be more efficient. The assignment-based version first called default constructors to initialize theName, theAddress, and thePhones, 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, because the arguments in the initilization list approach avoids that problem, because the arguments in the initialization list are used as constructor arguments for the various data members. In this case, theName is copy-constructed from name, theAddress is copy-constructed from address, and thePhones is copy-constructed from phones. For most types, a single call to a copy constructor is more efficient – sometimes much more efficient – than a call to the default constructor followed by a call to the copy assignemnet operator.

For objects of built-in type like numTimesConsulted, there is no difference in cost between initialization and assignment, but for consistence, it’s often best to initialize everything via member initialization. Similarly, you cn use the member initilzation list even when you want to default-construct a data member; just specify nothing as an initialization argument.
Oh it's this thing!
Yes, people really do need to know this. The body of the constructor performs assignments; only the initializer list can actually "initialize" the variable.
The constructor body can perform initialization, it's just not recommended since it makes for restrictions. An easy rule of thumb is ALWAYS use initialization lists.
Be weary of "always" rules.

I generally agree that you should try to prefer initialization lists, so I don't mean to disagree with you. In fact I strongly agree that initialization lists are a better solution most of the time. I'm just trying to point out some "gotchas":


There are caveats to the initializer list:

-) The items are not necessarily initialized in the order that they're listed in the initializer list. They're actually initialized in the order in which they're declared in the class. GCC (and maybe modern VS) will throw a warning/error if the orders mismatch, but it still something to be weary about. For example:



This above code is no good because 'count' is used to initialize 'vec' before count has been initialized (vec is initialized first because it was declared first).

Of course you could change the order in which your variables are declared. But then what happens if you change them again later? Or what if your initialization is more complex?


-) They can complicate multiple ctors and produce a lot of redundant code. Say you have a class that has 20 members and 5 different ctors (most of which are initialized the same way for every ctor).

Is it wise to repeat the initialzation for every ctor? I'd say no. Duplicate code is bad for many reasons... the biggest one is that it makes updating the code more difficult. What if you need to change initialization (like if you add a new member, or need to change the initialzation state slightly)? It's very easy to forget to change one of the many ctors, and if you do you have a gnarly bug that could rip you a new one and be hard to expose. Such a situation would be better met with a minimal initialzation list, and a call to a common "Init" function that does most of the initialization.

But I think this problem is solved by C++0x since it allows ctors to call other ctors, so this may be a moot point soon.

-) They can complicate multiple ctors and produce a lot of redundant code. Say you have a class that has 20 members and 5 different ctors (most of which are initialized the same way for every ctor).


That's the only somewhat legitimate reason, though that can somewhat be mitigated
by use of a helper base class (it seems like I just read something about this somewhere,
but at this late hour I can't remember what or where)

The first reason given should not be a problem as I would think all modern compilers
(even MSoft) would warn about initialization order.

Anyway, yes, C++0x has solved this problem.
They can complicate multiple ctors and produce a lot of redundant code. Say you have a class that has 20 members and 5 different ctors (most of which are initialized the same way for every ctor).


This can also be mitigated with modeling tools where you specify initial values for attributes and the tool autogenerates your constructors and their initialization lists. When working on large scale projects I have always used some kind of modeling tool. I think that it is really important to try to use the modeling tools to your advantage rather than to make the common function for initialization. I also think that it is pretty rare that you need many constructors for a class. To avoid this you can sometimes use default values for parameters so that some can be optionally specified by the caller instead of having many forms of the same constructor. Your default constructor could be a constructor where all of the args have default values. You generally run into the issue that disch is talking about when you are writing classes manually without the assistance of a complex IDE.
The constructor body can perform initialization, it's just not recommended since it makes for restrictions. An easy rule of thumb is ALWAYS use initialization lists.


Sorry but I have to correct this statement as it is very dangerous. As a matter of fact we just saw a thread in the beginner forum today where this mistake was made. The poster had created a variable in the body of the constructor with the same name as the class attribute thinking that they were initializing the class attribute. The only thing that you can initialize in the body of a constructor are things that are local to the body and they will be destroyed when the constructor finishes. As silly as this example seems to a pro, I have seen beginners make this mistake many times.

There is a difference in C++ between initialization and assignment. [/i]Initialization[/i]
occurs when an object is constructed. Assignment occurs after an object is already initialized.

Therefore, no, you cannot initialize a member in the body of the constructor; you can only assign
it.
Topic archived. No new replies allowed.

Constructor is a special non-static member function of a class that is used to initialize objects of its class type.

In the definition of a constructor of a class, member initializer list specifies the initializers for direct and virtual base subobjects and non-static data members. ( Not to be confused with std::initializer_list )

[edit]Syntax

Constructors are declared using member function declarators of the following form:

class-nameparameter-list(optional)except-spec(optional)attr(optional) (1)

Where class-name must name the current class (or current instantiation of a class template), or, when declared at namespace scope or in a friend declaration, it must be a qualified class name.

The only specifiers allowed in the decl-specifier-seq of a constructor declaration are friend, inline, and (in particular, no return type is allowed). Note that cv- and ref-qualifiers are not allowed either; const and volatile semantics of an object under construction don't kick in until the most-derived constructor completes.

The body of a function definition of any constructor, before the opening brace of the compound statement, may include the member initializer list, whose syntax is the colon character , followed by the comma-separated list of one or more member-initializers, each of which has the following syntax

class-or-identifierexpression-list(optional) (1)
class-or-identifierbrace-init-list (2) (since C++11)
parameter-pack (3) (since C++11)

1) Initializes the base or member named by class-or-identifier using direct initialization or, if expression-list is empty, value-initialization

2) Initializes the base or member named by class-or-identifier using list-initialization (which becomes value-initialization if the list is empty and aggregate-initialization when initializing an aggregate)

3) Initializes multiple bases using a pack expansion

class-or-identifier - any identifier, class name, or decltype expression that names a non-static data member, a direct or virtual base, or (for delegating constructors) the class itself
expression-list - possibly empty, comma-separated list of the parameters to pass to the constructor of the base or member
braced-init-list - brace-enclosed list of comma-separated initializers and nested braced-init-lists
parameter-pack - name of a variadic template parameter pack

Run this code

struct S {int n; S(int);// constructor declaration S(): n(7){}// constructor definition.// ": n(7)" is the initializer list// ": n(7) {}" is the function body}; S::S(int x): n{x}{}// constructor definition. ": n{x}" is the initializer listint main(){ S s;// calls S::S() S s2(10);// calls S::S(int)}

[edit]Explanation

Constructors have no names and cannot be called directly. They are invoked when initialization takes place, and they are selected according to the rules of initialization. The constructors without specifier are converting constructors. The constructors with a specifier make their type a . Constructors that may be called without any argument are default constructors. Constructors that take another object of the same type as the argument are copy constructors and move constructors.

Before the compound statement that forms the function body of the constructor begins executing, initialization of all direct bases, virtual bases, and non-static data members is finished. Member initializer list is the place where non-default initialization of these objects can be specified. For members that cannot be default-initialized, such as members of reference and const-qualified types, member initializers must be specified. No initialization is performed for anonymous unions or variant members that do not have a member initializer.

The initializers where class-or-identifier names a virtual base class are ignored during execution of constructors of any class that is not the most derived class of the object that's being constructed.

Names that appear in expression-list or brace-init-list are evaluated in scope of the constructor:

Exceptions that are thrown from member initializers may be handled by function-try-block

Member functions (including virtual member functions) can be called from member initializers, but the behavior is undefined if not all direct bases are initialized at that point.

For virtual calls (if the bases are initialized), the same rules apply as the rules for the virtual calls from constructors and destructors: virtual member functions behave as if the dynamic type of *this is the class that's being constructed (dynamic dispatch does not propagate down the inheritance hierarchy) and virtual calls (but not static calls) to pure virtual member functions are undefined behavior.

If a non-static data member has an default member initializer and also appears in a member initializer list, then member initializer list is executed and the default member initializer is ignored:

struct S {int n =42;// default member initializer S(): n(7){}// will set n to 7, not 42};
(since C++11)

Reference members cannot be bound to temporaries in a member initializer list:

Note: same applies to default member initializer

struct A { A(): v(42){}// Errorconstint& v;};
(since C++14)

Delegating constructor

If the name of the class itself appears as class-or-identifier in the member initializer list, then the list must consist of that one member initializer only; such constructor is known as the delegating constructor, and the constructor selected by the only member of the initializer list is the target constructor

In this case, the target constructor is selected by overload resolution and executed first, then the control returns to the delegating constructor and its body is executed.

Delegating constructors cannot be recursive.

Inheriting constructors

See using declaration.

class Foo {public: Foo(char x, int y){} Foo(int y): Foo('a', y){}// Foo(int) delegates to Foo(char,int)};
(since C++11)

[edit]Initialization order

The order of member initializers in the list is irrelevant: the actual order of initialization is as follows:

1) If the constructor is for the most-derived class, virtual base classes are initialized in the order in which they appear in depth-first left-to-right traversal of the base class declarations (left-to-right refers to the appearance in base-specifier lists)

2) Then, direct base classes are initialized in left-to-right order as they appear in this class's base-specifier list

3) Then, non-static data members are initialized in order of declaration in the class definition.

4) Finally, the body of the constructor is executed

(Note: if initialization order was controlled by the appearance in the member initializer lists of different constructors, then the destructor wouldn't be able to ensure that the order of destruction is the reverse of the order of construction)

[edit]Example

[edit]Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 1696 C++14 reference members could be initialized to temporaries (whose lifetime would end at the end of ctor) such init is ill-formed

[edit]References

  • C++11 standard (ISO/IEC 14882:2011):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]
  • C++98 standard (ISO/IEC 14882:1998):
  • 12.1 Constructors [class.ctor]
  • 12.6.2 Initializing bases and members [class.base.init]
class X {int a, b, i, j;public:constint& r; X(int i): r(a)// initializes X::r to refer to X::a , b{i}// initializes X::b to the value of the parameter i , i(i)// initializes X::i to the value of the parameter i , j(this->i)// initializes X::j to the value of X::i{}};
#include <fstream>#include <mutex>   struct Base {int n; Base(int n): n(n){}};   struct Class :public Base {unsignedchar x;unsignedchar y;std::mutex m;std::lock_guard<std::mutex> lg;std::fstream f1, f2;   Class (int x ): Base (123), // initialize base class x ( x ), // x (member) is initialized with x (parameter) y {0}, // y initialized to 0 f1{"test.cc", std::ios::app}, // this takes place after m and lg are initialized f2("test.bin", std::ios::binary|std::ios::app), lg ( m ), // lg uses m, which is already initialized m{}// m is initialized before lg even though it appears last here{}// empty compound statement   Class (double a ): Base (0), y ( a+1), x ( y ), // x will be initialized before y, its value here is indeterminate lg ( m ){}// base class constructor does not appear in the list, it is// default-initialized (not the same as if Base() were used, which is value-init)   Class()try// function-try block begins before the function body, which includes init list: Class(0.0), //delegate constructor f1(__func__)//__func__ is available because init-list is a part of constructor{// ...}catch(...){// exception occurred on initialization}};   int main(){ Class c; Class c1(1); Class c2(0.1);}
Categories: 1

0 Replies to “C++ Initialization List Assignment Operator”

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *