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
我們通過一個例子來分析具體細節:
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
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庫中出現大量的這種使用方式。請看下例:
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。統一初始化,完全不允許精度階段的發生,更進一步,要求引數列表中的所有引數的精度一樣。請看以下示例。
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