摘要:在C++11之后,聲明時初始化->初始化列表->構造函數初始化。
本文分享自華為雲社區《如何編寫高效、優雅、可信代碼系列(3)——類成員初始化的三種方式》,原文作者:我是一顆大西瓜。
首先,先得了解一下C++支持哪幾種類成員初始化的方式,你常用的又是哪一種。
- 初始化方式一:初始化列表
class A { public: int a; // 初始化列表 A(int a_):a(a_){} };
- 初始化方式二:構造函數初始化
class A { public: int a; // 初始化列表 A(int a_, bool b) { a = a_; } };
- 初始化方式三:聲明時初始化(也稱就地初始化,c++11后支持)
class A { public: int a = 1; // 聲明時初始化 A() {} };
在C++98中,支持了在類聲明中使用等號“=”加初始值的方式,來初始化類中靜態成員常量。這種聲明方式我們也稱之為“就地”聲明。就地聲明在代碼編寫時非常便利,不過C++98對類中就地聲明的要求卻非常高。如果靜態成員不滿足常量性,則不可以就地聲明,而且即使常量的靜態成員也只能是整型或者枚舉型才能就地初始化。而非靜態成員變量的初始化則必須在構造函數中進行。比如,如下代碼在c++98中編譯
class Init { public: Init(): a(0) [] Init(int d): a(d) {} private: int a; const static int b = 0; int c = 1; // member, cannot pass build static int d = 0; // member, cannot pass build static const double e = 1.3; // not int or enum type, cannot pass build stati const char* const f = "e"; // not int or enum type, cannot pass build }
這非常不方便,所以在C++11中,標准允許非靜態成員變量的初始化有多種形式。具體而言,除了初始化列表外,在C++11中,標准還允許使用等號= 或者 花括號{} 進行就地的非靜態成員變量初始化。
struct init { int a = 1; double b {1.2}; };
大家知道,有幾種情況下推薦優先使用列表初始化
- const成員變量只能用成員初始化列表來完成初始化,而不能在構造函數內賦值
- 初始化的數據成員是對象
- 需要初始化引用成員數據
具體的原因這里不細述,大家可以去看一下《C++ Primer》。
構造函數初始化的本質是賦值操作("="),這個方法存在兩個問題,一個是比起初始化列表和就地初始化,此方式的效率偏低;第二個是可能存在錯誤隱患。
先說第一個,賦值過程中會產生臨時對象,臨時對象的構造析構會造成效率損耗,初始化列表的方式就避免了產生臨時對象縮帶來的問題。
第二個是,如果你沒有重寫或者禁止賦值構造函數,c++會悄悄的加上默認的賦值構造函數,這個時候也有可能帶來問題。
從C++11之后,這三種初始化的方法都可以使用,並不會存在沖突,但是,他們之間是有優先級順序的,這個優先級來源於他們在初始化的時間順序,后面初始化的會把前面的覆蓋掉,成員變量的初始化順序是
聲明時初始化->初始化列表->構造函數初始化
因此假如三種初始化方式同時存在的話,那么最后保留的成員變量值肯定是構造函數中初始化的值。
#include <iostream> using namespace std; class A { public: int a = 1; A(int a_) :a(2) { a = 3; } }; int main() { A a; cout << "a.a=" << a.a << endl; return 0; } // a.a=3
既然初始化方式這么多,那么什么時候適用哪種呢?
1. 聲明時初始化的使用場景
- 一個優點是直觀,你在聲明的時候順便給一個初始值,bravo,別人在看你代碼的時候,點一下調到聲明也能看到你賦予的初始值,不用再去看構造函數那里給的什么值
- 第二個優點更有用了,比如你要定義多個構造函數,每個構造函數都用列表初始化的方法初始化,多麻煩呀,請看下面的例子,媽媽看了再也不用擔心我想用其他初始化方法了
class Group { public: Group() {} Group(int a): data(a) {} Group(Mem m): mem(m) {} Group(int a, Mem m, string n): data(a), mem(m), name(n) {} private: int data = 1; Mem mem{0}; string name{"Group"}; };
2. 列表初始化的使用場景
前面說過了三個場景,這里贅述一下
- const成員變量只能用成員初始化列表來完成初始化,而不能在構造函數內賦值
- 初始化的數據成員是對象
- 需要初始化引用成員數據
但是,需要注意列表初始化的順序,不過IDE會提示你的
3. 構造函數初始化的使用場景
- 第一個就是拷貝和賦值構造函數里(不然怎么叫賦值構造函數呢)
- 第二個就是比較無聊的情況了,比如你想把幾個成員函數都初始化成一個值,請看下面例子
class Group { public: Group() {data1 = data2 = data3 = 0;} private: int data1; int data2; int data3; };
一言以蔽之,優先就地初始化和列表初始化。