C++11 帶來的新特性 (2)—— 統一初始化(Uniform Initialization)

1 統一初始化(Uniform Initialization)

在C++ 11以前,全部對象的初始化方式是不一樣的,常常讓寫代碼的咱們感到困惑。C++ 11努力創造一個統一的初始化方式。
其語法是使用{}和std::initializer_list ,先看示例。 數組

int values[]{ 1, 2, 3 };
    std::vector<int> v{ 2, 3, 6, 7 };
    std::vector<std::string> cities{
        "Berlin", "New York", "London",  "Braunschweig"
    };
    std::comples<double> c{4.0, 3.0}; //等價於 c(4.0, 3.0)
    auto ar = { 1, 2, 3 };  // ar 是一個std::initializer_list<int>類型

    std::vector<int> v = { 1, 2, 3 };
    std::list<int> l = { 1, 2, 3 };
    std::set<int> s = { 1, 2, 3 };
    std::map<int, std::string> m = { {1, "a"}, {2, "b"} };

2 原理

針對形如"{ 1, 2, 3 }"的參數列表,系統會首先自動調用參數初始化(value initialization),將其轉換成一個std::initializer_list ,它將用於對變量(多是簡單類型,或者對象類型)初始化。好比"{ 1, 2, 3 }"會首先生成一個std::initializer_list ,而後用於生成一個int數組values。c{4.0, 3.0}會生成一個std::initializer_list ,而後調用std::comples 類的構造函數std::comples (double,double)。
咱們經過一個例子來分析具體細節:
dom

std::vector<int> v{ 2, 3, 6, 7 };
  • 首先,將參數列表{ 2, 3, 6, 7 }轉換成std::initializer_list
    從stl源碼中能夠看出initializer_list的帶參數構造函數是個私有函數,它只能由編譯器調用。
private:
      iterator          _M_array;
      size_type         _M_len;

      // The compiler can call a private constructor.
      constexpr initializer_list(const_iterator __a, size_type __l)
      : _M_array(__a), _M_len(__l) { }
  • 其次,使用std::initializer_list 對象來初始化std::vector 類的構造函數。下面是構造函數源碼。
vector(initializer_list<value_type> __l,
         const allocator_type& __a = allocator_type())
      : _Base(__a)
      {
    _M_range_initialize(__l.begin(), __l.end(),
                random_access_iterator_tag());
      }

3 未賦值的初始化

若是使用了std::initializer_list ,可是沒有指定參數值,結果會怎樣?直接看示例。 函數

int i;      //i值未定義
    int j{};    //j=0
    int *p;     //p值未定義
    int *q{};   //q=nullptr

4 在構造函數中顯示使用std::initializer_list

咱們能夠在構造函數中主動使用std::initializer_list ,這時外部調用{}初始化時,會優先調用包含std::initializer_list 參數的構造函數。請看下例子。 翻譯

class P
    {
    public:
        P(int, int){std::cout<<"call P::P(int,int)"<<std::endl;}
        P(std::initializer_list<int>){
            std::cout<<"call P::P(initializer_list)"<<std::endl;
        }
    };
    P p(77,5);     // call P::P(int,int)
    P q{77,5};     // call P::P(initializer_list)
    P r{77,5,42};  // call P::P(initializer_list)
    P s = {77, 5}; // call P::P(initializer_list)

5 拒絕隱式調用構造函數

咱們知道,C++會先使用{}中的參數生成一個std::initializer_list 對象,而後調用賦值對象的構造函數。
有一種特殊情形,咱們若是但願對象的某個構造函數必需要被顯示調用,如何作到呢?向其中添加一個explicit關鍵字。在stl庫中出現大量的這種使用方式。請看下例:
code

class P
    {
      public:
        P(int a, int b){...}
        explicit P(int a, int b, int c){...}
    };

    P x(77,5);    //OK
    P y{77,5);    //OK
    P z{77,5,42}; //OK
    p v = {77,5}; //OK,(implicit type conversion allowed)
    P w = {77,5,42};//Error,必需要顯示調用該構造函數

    void fp(const P&);
    
    fp({47,11});    //OK
    fp({47,11,3});  //Error
    fp(P{47,11});   //OK
    fp(P{47,11,3}); //OK

6 侷限 —— Narrowing Initializations

統一初始化用起來很舒爽,那它有什麼侷限呢?
有,在一種場景下沒法使用,那就是Narrowing Initializations。
Narrowing Initializations,我翻譯爲「精度截斷」。好比float轉換爲int,double轉換爲float。統一初始化,徹底不容許精度階段的發生,更進一步,要求參數列表中的全部參數的精度同樣。請看如下示例。orm

int x1(5.3);    //OK, x1 = 5.3
    int x3{5.0};    //Error
    int x4 = {5.3}; //Error
    char c1{7};     //OK
    char c2{99999}; //Error
    std::vector<int> v1{ 1, 2, 4, 5};    //OK
    std::vector<int> v2{ 1, 2,3, 4, 5.6};//Error

可是若是實際工程容許精度截斷的發生,那麼咱們應該怎麼完成初始化。可使用()來完成初始化,它會調用賦值操做或者相應的構造函數。對象

int x3{5.0};    //Error
    int x2(5.2);    //OK, x2 = 5
相關文章
相關標籤/搜索