C++11 constructors

| category: Programming | author: st
Tags: ,

According to specification, "...constructor is a special non-static member function of a class that is used to initialize objects of its class type". In addition to basic concepts, C++11 introduces move constructors, initializer list type and brace-enclosed lists of comma-separated initializers.

However the impact of introducing new constructors combining with initializer lists may be unexpected in some cases. For example, the using of brace-enclosed lists can call constructors with corresponding parameters if there is no initializer list constructor is defined. When an initializer list constructor is introduced, the only way to call other constructors with parameters is do not use the braced list.

The following unit test can help you to understand new constraint. Like the auto type, brace-enclosed list should be used only when they are really required.

#include <iostream>
#include <cstdlib>
#include <cassert>

class MyObj
{
public:
    MyObj() : val(1) { std::cout << "MyObj()";}
    MyObj(int i) : val(2) { std::cout << "MyObj(int i)"; }
    MyObj(int i, int j) : val(3) { std::cout << "MyObj(int i, int j)"; }
    MyObj(std::initializer_list<int> params) : val(4) { std::cout << "MyObj(std::initializer_list<int> params)"; }
    MyObj(const MyObj& source) : val(5) { std::cout << "MyObj(const MyObj& source)"; }
    MyObj(MyObj&& source) : val(6) { std::cout << "MyObj(MyObj&& source)"; }
    MyObj& operator= (MyObj& source) { std::cout << "MyObj copy assignement"; }
    MyObj& operator= (MyObj&& source) { std::cout << "MyObj move assignement"; }
    int val = -1;
};

int main()
{
    std::cout << "MyObj o11         : "; MyObj o11; std::cout << std::endl; assert(o11.val == 1);
    std::cout << "MyObj o12()       : "; MyObj o12(); std::cout << "considered as function prototype" << std::endl;
    std::cout << "MyObj o13{}       : "; MyObj o13{}; std::cout << std::endl; assert(o13.val == 1);
    std::cout << "MyObj o14 = {}    : "; MyObj o14 = {}; std::cout << std::endl; assert(o14.val == 1);
    std::cout << "MyObj o21(11)     : "; MyObj o21(11); std::cout << std::endl; assert(o21.val == 2);
    std::cout << "MyObj o22 = 11    : "; MyObj o22 = 11; std::cout << std::endl; assert(o22.val == 2);
    // May be not expected when introducing new constructor with initializer list params
    std::cout << "MyObj o23{11}     : "; MyObj o23{11}; std::cout << std::endl; assert(o23.val == 4);
    // The only way to call "old" two-params constructor
    std::cout << "MyObj o31(1, 2)   : "; MyObj o31(1, 2); std::cout << std::endl; assert(o31.val == 3);
    // Initializer list constructor is expected
    std::cout << "MyObj o32 = {1, 2}: "; MyObj o32 = {1, 2}; std::cout << std::endl; assert(o32.val == 4);
    std::cout << "MyObj o33{1, 2}   : "; MyObj o33{1, 2}; std::cout << std::endl; assert(o33.val == 4);
    std::cout << "MyObj o34({1, 2}) : "; MyObj o34({1, 2}); std::cout << std::endl; assert(o34.val == 4);
    // Copy constructor is expected
    std::cout << "MyObj o41(o11)    : "; MyObj o41(o11); std::cout << std::endl; assert(o41.val == 5);
    std::cout << "MyObj o42{o11}    : "; MyObj o42{o11}; std::cout << std::endl; assert(o42.val == 5);
    std::cout << "MyObj o43({o11})  : "; MyObj o43({o11}); std::cout << std::endl; assert(o43.val == 5);
    std::cout << "MyObj o44 = o11   : "; MyObj o44 = o11; std::cout << std::endl; assert(o44.val == 5);
    std::cout << "MyObj o45 = {o11} : "; MyObj o45 = {o11}; std::cout << std::endl; assert(o45.val == 5);
    // Move constructor is expected
    std::cout << "MyObj o51(std::move(o11))  : "; MyObj o51(std::move(o11)); std::cout << std::endl; assert(o51.val == 6);
    std::cout << "MyObj o52{std::move(o11)}  : "; MyObj o52{std::move(o11)}; std::cout << std::endl; assert(o52.val == 6);
    std::cout << "MyObj o53({std::move(o11)}): "; MyObj o53({std::move(o11)}); std::cout << std::endl; assert(o53.val == 6);
    std::cout << "MyObj o54 = std::move(o11) : "; MyObj o54 = std::move(o11); std::cout << std::endl; assert(o54.val == 6);
}

Test results

MyObj o11         : MyObj()
MyObj o12()       : considered as function prototype
MyObj o13{}       : MyObj()
MyObj o14 = {}    : MyObj()
MyObj o21(11)     : MyObj(int i)
MyObj o22 = 11    : MyObj(int i)
MyObj o23{11}     : MyObj(std::initializer_list<int> params)
MyObj o31(1, 2)   : MyObj(int i, int j)
MyObj o32 = {1, 2}: MyObj(std::initializer_list<int> params)
MyObj o33{1, 2}   : MyObj(std::initializer_list<int> params)
MyObj o34({1, 2}) : MyObj(std::initializer_list<int> params)
MyObj o41(o11)    : MyObj(const MyObj& source)
MyObj o42{o11}    : MyObj(const MyObj& source)
MyObj o43({o11})  : MyObj(const MyObj& source)
MyObj o44 = o11   : MyObj(const MyObj& source)
MyObj o45 = {o11} : MyObj(const MyObj& source)
MyObj o51(std::move(o11))  : MyObj(MyObj&& source)
MyObj o52{std::move(o11)}  : MyObj(MyObj&& source)
MyObj o53({std::move(o11)}): MyObj(MyObj&& source)
MyObj o54 = std::move(o11) : MyObj(MyObj&& source)