C++ 11 Uniform initialization

Discussion in 'C++' started by BiplabKamal, Apr 6, 2016.

  1. C++ has a lot of way of initializing an object but prior to C++11 there was no single way of initialization applicable to all type of objects. That was the motivation to find a single way of initialization in C++11.

    Novice programmers may get confused with initialization and assignment operations. When we initialize with an assignment (=) sign someone may take it as assignment operation. But remember assignment operation and initialization operation are different-
    Code:
    int i = 0; // It is not assignment but initialization
    int j = i; //It is again initialization
    int k; //Uninitialized
    k = j; // This is not initialization but assignment
    
    The difference may be academic for built-in types but for user defined type like class, it is very important to remember that different methods are called for different operations. For initialization constructor, copy constructor or move constructor are called and for assignment, assignment operator or move assignment operator are called. The following code snippet using a class MyClass is showing how initialization and assignment differs:
    Code:
    //Initialization
    MyClass obj1; // Constructor is called
    MyClass obj2 = obj1; // Copy constructor is called
    MyClass obj3 = std::move(obj1); // Move constructor is called
    
    // Assignment
    obj2 = obj1; //Assignment operator is called
    obj2 = MyClass();// Move assignment operator is called
    
    Want to run and see the code? Here is the class definition:

    Code:
    #include<iostream>
    #include<vector>
    using namespace std;
    class MyClass
    {
    private:
        static int instancecount;
        const int data;
    
    public:
        //Default constructor
        MyClass() :data(0)
        {
            cout << "+ Default Constructor()" << endl;
            cout << "New instance of MyClass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
            cout << "- Default Constructor()" << endl<<endl;
        }
    
        //Parameterized constructor
        MyClass(int value) :data(value)
        {
            cout << "+ Parameterize Constructor()" << endl;
            cout << "New instance of MyClass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
            cout << "- Parameterize Constructor()" << endl<<endl;
        }
        //Default copy constructor
        MyClass(const MyClass& source) :data(source.data)
        {
            cout << "+ Default copy  Constructor ()" << endl;
            cout << "New instance of MyClass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
            cout << "- Default copy  Constructor ()" << endl<<endl;
        }
        //Move constructor
        MyClass(MyClass&& source) :data(source.data)
        {
            cout << "+ Move Constructor ()" << endl;
            cout << "New instance of MyClass is created " << endl;
            cout << "Number of instances: " << ++instancecount << endl;
            cout << "- Move Constructor ()" << endl<<endl;
        }
        //Default copy assignment operator
        MyClass& operator =(MyClass& source)
        {
            cout << "+ copy  assignment operator ()" << endl;
            
            cout << "- copy  assignment operator ()" << endl<<endl;
            return *this;
        }
        //Move assignment operator
        MyClass& operator =(MyClass&& source)
        {
            cout << "+ Move  assignment operator ()" << endl;
            
            cout << "- Move  assignment operator ()" << endl << endl;
            return *this;
        }
        ~MyClass()
        {
            cout << "+ Destructor ()" << endl;
            cout << "One instance of MyClass is destroyed" << endl;
            cout << "Number of instances: " << --instancecount << endl;
            cout << "- Destructor ()" << endl<<endl;
        }
    
    };
    int MyClass::instancecount = 0; //intializayion
    
    Now we know the difference of initialization and assignment, Let us see what are the ways to initialize a variable:
    Code:
    int i= 0; //Initialize to zero
    int i(0); //Initialize to zero
    int i = int(0); //Initialize to zero
    int i{ 0 }; //Initialize to zero
    
    Code:
    MyClass obj(); //It treats as a function deceleration, confusing, isn’t it?
    MyClass obj(0); //Create object and calls constructor
    MyClass obj1(obj);//Create object and calls copy constructor
    MyClass obj1=obj;//Create object and calls copy constructor
    MyClass obj1 = MyClass();//Create object and calls constructor
    
    So which method do you adopt for initialization? Every time you write code, you might be wasting time to think about which initialization is to be used. Even you fix your mind to use only one method there is no single way to initialize all type of data structures in C++98.For example if you declare a vector with initial set of value you cannot do it in C++98. You have to write following code-
    Code:
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    
    But in C++11 you can initialize it:
    Code:
    std::vector<int> v{1,2,3,5}; // v will initially have 4 items and contents will be 1,2,3 and 5. 
    
    This is called brace initialization. C++11 made the brace initialization applicable to all type of data and recommends to use it and not use other methods. This uniform initialization method is solving 2 problems, one is to remove the confusion of selecting of a particular initialization method and the other is initializing is possible for types which was not possible previously: Following decelerations show how braced initialization is used for all types of data:
    Code:
    int i{ 0 }; //Initialize to zero
    int j{}; //Initialize to zero
    std::vector<int>v{ 1,3,5 };// Initialize the vector with 3 elements
    std::vector<MyClass>{}; //Empty vector
    std::vector<MyClass>{MyClass{ 1 }, MyClass{ 2 }, MyClass{3}};// Initialize the vector with 3 MyClass elements
    MyClass obj{ }; //Create object and calls default constructor, if default constructor is not there then error
    MyClass obj1{ 10 }; //Create object and calls constructor with one argument
    MyClass obj2{ obj };//Create object and calls copy constructor
    MyClass obj3{ std::move(obj) };//Create object and calls move constructor
    struct S
    {
        int x;
        int y;
    };
    S s{ 2 };//Initializes s with x=2,y=0
    S s{ 2,4 };//Initializes s with x=2,y=4
    S s{ 2,4,5 };//Error: too many initializer values
    int arr[3]{ 2,4,6 };//Initializes arr with 2,4 and 6
    int arr[3]{ 2,4 };//Initializes arr with 2,4 and 0
    int arr[3]{ 2,4,6,8 };//Error: too many initializer values
    string str{"Hello"}; // Uses constructor
    string str2{str}; // Uses copy constructor
    map<int, string> mymap{ {1,"Aple"},{2,"Orrange"},{3,"Mango"} };// Creates and     initialises the map of size 3
    int* p = new int[3]{ 1,5,8 };//Allocates memory for elements and intializes the     elemnts to 1,5 and 8
    int *ptr{};//initializes the prt to nulptr
    char s2[10]{};//Initializes all characters to '\0'
    class C1
    {
        int val[3]{0,1,2}; // member initialization
    public:
        C1() : val{ 0,1,2 } {};//Over rides the member initialization
    };
    
    Braced initialization prohibits implicit narrowing conversions among built-in types. For example:
    Code:
    int i = 3 + 4.5 + 4; // OK
    int i2 (3 + 4.5 + 4);//OK
    int j{ 3 + 4.5 + 4 };// Error: narrowing conversion not allowed
    
    With braced initialization you can avoid ambiguous syntax of parenthesis initialization like:
    Code:
    MyClass obj(0);// Calls the constructor with one argument
    MyClass obj();// Does not create MyClass object rather declares a function obj()     which returns MyClass
    
    With braced initialization it works-
    Code:
    MyClass obj{ 0 };// Calls the constructor with one argument
    MyClass obj{};// Calls the default constructor
    
    Understanding the braced initialization process will not be complete unless you know about std::initializer_list<T> template and it’s relation with braced initialization ( {} ). Look at the following code:
    Code:
    auto mylist={1,2,3,4};
    
    Here the type of mylist is deduced to be std::initializer_list<int>.

    Actually braced initialization creates a std::initializer_list object by default if not specified otherwise. If you have overloaded methods with std::initializer_list as argument for one of the overloaded methods, braced initialization will match the version with std::initializer_list as argument. Suppose you have the following two overloaded functions -
    Code:
    void myfn(vector<int> v)
    {
        cout << "Calling with vector argument" << endl;
    }
    
    void myfn(initializer_list<int> il)
    {
        cout << "Calling with list argument" << endl;
    }
    
    And you call the finction like: myfn({1, 2, 3}); will invoke the 2nd function. This scenario you will face while initializing a vector shown below
    Code:
    vector<int> v(10, 4);//Will call the constructor which adds 10 elements and     initialize each element with value 4
    vector<int> v2{ 10,4 };//Will call the constructor with std::initializer_list<int>     which add 2 element 10 and 4
    
    This behavior can lead to unexpected behavior when braced initialization is used with class having overloaded constructors having std::initializer_list<T> type argument. While implementing a class having overloaded constructors or methods with std::initializer_list<T> type argument you should be aware of the implication of braced initialization.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice