Where How to Use List Initialization

Why should I prefer to use member initialization lists?

For POD class members, it makes no difference, it's just a matter of style. For class members which are classes, then it avoids an unnecessary call to a default constructor. Consider:

class A
{
public:
A() { x = 0; }
A(int x_) { x = x_; }
int x;
};

class B
{
public:
B()
{
a.x = 3;
}
private:
A a;
};

In this case, the constructor for B will call the default constructor for A, and then initialize a.x to 3. A better way would be for B's constructor to directly call A's constructor in the initializer list:

B()
: a(3)
{
}

This would only call A's A(int) constructor and not its default constructor. In this example, the difference is negligible, but imagine if you will that A's default constructor did more, such as allocating memory or opening files. You wouldn't want to do that unnecessarily.

Furthermore, if a class doesn't have a default constructor, or you have a const member variable, you must use an initializer list:

class A
{
public:
A(int x_) { x = x_; }
int x;
};

class B
{
public:
B() : a(3), y(2) // 'a' and 'y' MUST be initialized in an initializer list;
{ // it is an error not to do so
}
private:
A a;
const int y;
};

Where can we use list initialization?

C++03

List initialization

In C++03 you can only use list-initialization for aggregates (C++03 [dcl.init.aggr]) and scalar (C++03 [dcl.init]/13) types:

int i = { 0 };
POD pod = { 0, 1, 2 };

List assignment

You could not use "list-assignment" anywhere in C++03. The grammar shown in [expr.ass]/1 does not allow a braced list on the right of an assignment.

C++11

List initialization

In C++11 you can use list-initialization pretty much anywhere you can create a variable (see [dcl.init] in C++11 and [dcl.init.list]/1 which lists contexts where list-initialization is allowed) e.g.

struct Base { };

struct Class : Base
{
int mem{ 0 }; // init non-static data member

Class(int i)
: Base{} // init base class
, mem{i} // init member
{
int j{i}; // init local var

int k = int{0}; // init temporary

f( { 1 } ); // init function arg

int* p = new int{1}; // new init

// int k(int()); // most vexing parse, declares function
int k{ int{} }; // ok, declares variable

int i[4]{ 1,2,3,4 }; // init array
}

Class f(int i)
{
return { i }; // init return value
}
};

Class c{1}; // init global var

Most of the initializations above declare an int or array of int but the same syntax can be used to call a constructor for a class type (like the two lines that construct a Class variable)

As well as being valid in almost any context where you can initialize a variable, list-initialization also interacts well with another new feature of C++11: the std::initializer_list class template. A constructor that takes a std::initializer_list argument can be passed an arbitrarily-long list of values, which the constructor can iterate over via begin() and end() member functions of the std::initializer_list. The main benefit of this new feature is that it allows you to initialize a container with a set of elements, e.g. vector<int> v{ 0, 1, 2, 3, 4, 5 } rather than constructing the container and then inserting values.

List-initialization can also be used for elements within a braced-init-list, allowing nested list-initialization e.g. Map m{ {a, b}, {c, d} } rather than Map m{ Map::value_type(a, b), Map::value_type(c, d) }

The only time list-initialization doesn't do the right thing is when trying to construct a class type by calling a constructor if the class has another constructor taking a std::initializer_list, as list-initialization will always prefer the constructor taking a std::initializer_list e.g.

// attempts to create vector of 5 elements, [1,1,1,1,1]
// but actually creates a vector with two elements, [5,1]
std::vector<int> v{ 5, 1 };

This doesn't call the vector(size_type, const int&) constructor, instead of calls the vector(initializer_list<int>) constructor.

List assignment

In C++11 you can use "list-assignment"

  • when assigning to a scalar type, if the braced-init-list has a single element that is convertible (without narrowing) to the variable's type (see [expr.ass]/9)
  • when the left operand of the assignment is a class type with a user-defined assignment operator, in which case the braced-init-list is used to initialize the argument of the operator (see [expr.ass]/9). This includes both cases like operator=(std::initializer_list<T>) where the elements of the braced-init-list in the right operand are convertible to T, e.g. for the std::vector<int> v above, v = { 1, 2, 3 } will replace the container's contents with [1,2,3] and when the braced-init-list can be implicitly-converted to the operator's argument type, via a suitable constructor e.g.

    struct A {
    int i;
    int j;
    };

    struct B {
    B& operator=(const A&);
    };

    int main() {
    B b;
    b = { 0, 1 };
    }

    On the last line of main the braced-init-list will be implicitly-converted to a temporary A then the assignment operator of B will be called with that temporary as its argument.

why is list initialization not invoked when initialize this class?

Myclass does have a default constructor; which is just marked as delete explicitly. So the effect of value-initialization should be:

  1. if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;

In default-initialization the deleted default constructor is selected and the program is ill-formed.

If not to declare the default constructor as

class Myclass {
public:
// Myclass() = delete;
Myclass(Myclass &&m) {}
Myclass(const Myclass &m) {}
Myclass(const std::initializer_list<int> &l) { std::cout << "initializer list"; }
};

Then Myclass doesn't have default constructor; (and no implicitly-declared default constructor because of other user-declared constructors). Then list-initialization is performed (as you expected), as the effect

All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list

Use of Initialization List

If those variables are fundamental types, and they are not const and not references, then there is no difference.

If your member is a reference or it is const, then you will have to initialize them, and that can only be done through an in initialization list, for the same reason that forbids you creating an uninitialized reference or const variable of a fundamental type:

int& x; // ERROR!
const int y; // ERROR!

Those objects cannot be re-assigned after initialization: thus, it makes sense that they have to be value-initialized, and that you won't be allowed to execute the body of your class's constructor with such member variables uninitialized.

Therefore, when you do have a choice, for fundamental types there is no difference between the two forms - just keep in mind that there are situations where you do not have a choice.

On the other hand, if your member variables are of class type, then the difference is that without an initialization list they will be default-constructed (if possible at all) and then assigned in the constructor's body; with an initialization list, they will be directly constructed with the arguments you specify.

Notice that C++11 gives you an additional possibility:

class MyClass {
public:
int m_classID = -1;
void *m_userdata = nullptr;
};

C++ With Initializer List a thing, when to use normal constructor?

Initialization list is about calling ctor of member variables. If you assign, you are altering the value of instance by using assign function. Obviously, these two are different functions.

There are a few cases that you cannot assign value to member variable in the ctor.

  • When the member variable is const.
  • When the member variable is an instance of class without default
    ctor.
  • When the member variable is a reference (same reason as above)
  • Initializing base class

When you create an instance without init-list, the member variable runs its ctor and then assign if you give value to it. It's a subtle difference but it might incur some penalty as the ctor runs first, and assign runs 2nd - which is unnecessary overhead.

List initialization (aka uniform initialization) and initializer_list?

List-initialization prefers constructors with a std::initializer_list argument. From cppreference:

The effects of list-initialization of an object of type T are:

[...cases that do not apply here ...]

  • Otherwise, the constructors of T are considered, in two phases:
    • All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have
      default values, are examined, and matched by overload resolution
      against a single argument of type std::initializer_list

    • If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments
      that consists of the elements of the braced-init-list, with the
      restriction that only non-narrowing conversions are allowed. If this
      stage produces an explicit constructor as the best match for a
      copy-list-initialization, compilation fails (note, in simple
      copy-initialization, explicit constructors are not considered at all).

While direct initialization does not prefer the initializer_list constructor. It calls the constructor that takes the size as argument.

And from https://en.cppreference.com/w/cpp/container/vector/vector:

Note that the presence of list-initializing constructor (10) means
list initialization and direct initialization do different things:

std::vector<int> b{3}; // creates a 1-element vector holding {3}
std::vector<int> a(3); // creates a 3-element vector holding {0, 0, 0}
std::vector<int> d{1, 2}; // creates a 2-element vector holding {1, 2}
std::vector<int> c(1, 2); // creates a 1-element vector holding {2}


And how can I call std::vector::vector(size_t) with list-initialization?

As explained above, the presence of the std::initializer_list constructor prevents you from calling the other constructor via list-initialization.



Related Topics



Leave a reply



Submit