統一初始設定和委派建構函式
在現代 C++,您可以為任何型別使用 括號初始化 ,但其中不含等號。 此外,當您使用多個可以簡化工作的建構函時候,您也可以使用委派的建構函式以簡化您的程式碼。
括號初始化
您可以為所有類別、結構或等位使用括號來初始化。 如果型別有預設建構函式,無論是隱含或是明確宣告,您可以使用預設括號初始化 (包含空括號)。 例如,使用預設和非預設的括號初始化,下列類別可能初始化:
#include <string>
using namespace std;
class class_a {
public:
class_a() {}
class_a(string str) : m_string{ str } {}
class_a(string str, double dbl) : m_string{ str }, m_double{ dbl } {}
double m_double;
string m_string;
};
int main()
{
class_a c1{};
class_a c1_1;
class_a c2{ "ww" };
class_a c2_1("xx");
// order of parameters is the same as the constructor
class_a c3{ "yy", 4.4 };
class_a c3_1("zz", 5.5);
}
如果一個類別有非預設的建構函式,那類別成員出現在brace initializer 的順序會跟相關參數在建構函式中出現的順序一樣, 而不是成員被宣告的順序 (如上述範例的 class_a )。 否則,如果這個型別沒有宣告建構函式,成員會出現在括號初始設定式的命令不相同則為其宣告命令; 在這種情況下,您可以如您所願使用許多公用成員,您不能略過任何成員。 下列範例顯示用以括號初始化的命令,而未宣告的建構函式時:
class class_d {
public:
float m_float;
string m_string;
wchar_t m_char;
};
int main()
{
class_d d1{};
class_d d1{ 4.5 };
class_d d2{ 4.5, "string" };
class_d d3{ 4.5, "string", 'c' };
class_d d4{ "string", 'c' }; // compiler error
class_d d5("string", 'c', 2.0 }; // compiler error
}
如果預設建構函式明確地宣告,但是標記為刪除,預設無法使用括號初始化:
class class_f {
public:
class_f() = delete;
class_f(string x): m_string { x } {}
string m_string;
};
int main()
{
class_f cf{ "hello" };
class_f cf1{}; // compiler error C2280: attempting to reference a deleted function
}
您可以使用括號來初始化任何地方初始化中通常會,比如函式參數或傳回值,或使用 new 關鍵字:
class_d* cf = new class_d{4.5};
kr->add_d({ 4.5 });
return { 4.5 };
initializer_list 建構函式
initializer_list Class 表示可用於建構函式所指定型別的物件清單和其他內容。 您可以使用括號初始化以建構 initializer_list:
initializer_list<int> int_list{5, 6, 7};
重要
若要使用這個類別,您必須包含 <initializer_list> 標頭。
initializer_list 可以複製。 在這個案例中,新清單的成員是對原始清單成員的參考:
initializer_list<int> ilist1{ 5, 6, 7 };
initializer_list<int> ilist2( ilist1 );
if (ilist1.begin() == ilist2.begin())
cout << "yes" << endl; // expect "yes"
標準文件庫容器類別和 ( string、 wstring和 regex,具有 initializer_list 建構函式。 下列範例示範如何使用這些建構函式的括號初始化:
vector<int> v1{ 9, 10, 11 };
map<int, string> m1{ {1, "a"}, {2, "b"} };
string s{ 'a', 'b', 'c' };
regex rgx{'x', 'y', 'z'};
委派建構函式
許多類別擁有相同項目中的多個建構函式,以驗證參數:
class class_c {
public:
int max;
int min;
int middle;
class_c() {}
class_c(int my_max) {
max = my_max > 0 ? my_max : 10;
}
class_c(int my_max, int my_min) {
max = my_max > 0 ? my_max : 10;
min = my_min > 0 && my_min < max ? my_min : 1;
}
class_c(int my_max, int my_min, int my_middle) {
max = my_max > 0 ? my_max : 10;
min = my_min > 0 && my_min < max ? my_min : 1;
middle = my_middle < max && my_middle > min ? my_middle : 5;
}
};
您可以透過增加一個進行驗證的函式以減少程式碼,不過,如果建構函式可以委派到其他工作,了解和維護class_c 的程式碼將更為容易。 若要加入委派的建構函式,請使用 constructor (. . .) : constructor (. . .) 語法:
class class_c {
public:
int max;
int min;
int middle;
class_c(int my_max) {
max = my_max > 0 ? my_max : 10;
}
class_c(int my_max, int my_min) : class_c(my_max) {
min = my_min > 0 && my_min < max ? my_min : 1;
}
class_c(int my_max, int my_min, int my_middle) : class_c (my_max, my_min){
middle = my_middle < max && my_middle > min ? my_middle : 5;
}
};
int main() {
class_c c1{ 1, 3, 2 };
}
您透過前導參照逐步執行,請注意建構函式 class_c(int, int, int) 第一次呼叫建構函式 class_c(int, int),接著呼叫 class_c(int)。 每個建構函式會執行未由其他建構函式只執行的工作。
第一個被呼叫建構函式的稱為初始化物件,他所有的成員都將被初始化。 您無法在委派至另一個建構函式的成員初始設定,如下所示:
class class_a {
public:
class_a() {}
// member initialization here, no delegate
class_a(string str) : m_string{ str } {}
//can’t do member initialization here
// error C3511: a call to a delegating constructor shall be the only member-initializer
class_a(string str, double dbl) : class_a(str) , m_double{ dbl } {}
// only member assignment
class_a(string str, double dbl) : class_a(str) { m_double = dbl; }
double m_double{ 1.0 };
string m_string;
};
下一個範例顯示使用非靜態資料成員初始設定式。 請注意,如果建構函式初始化一個已有給定值的成員,則初始化的同時,此成員會被覆寫:
class class_a {
public:
class_a() {}
class_a(string str) : m_string{ str } {}
class_a(string str, double dbl) : class_a(str) { m_double = dbl; }
double m_double{ 1.0 };
string m_string{ m_double < 10.0 ? "alpha" : "beta" };
};
int main() {
class_a a{ "hello", 2.0 }; //expect a.m_double == 2.0, a.m_string == "hello"
int y = 4;
}
建構函式委派語法不會預防意外產生的遞迴建構函式—Constructor1呼叫已經呼叫Constructor1的Constructor2—而且直到堆疊溢位之前都不會擲回任何錯誤訊息。 避免循環為您的責任。
class class_f{
public:
int max;
int min;
// don't do this
class_f() : class_f(6, 3){ }
class_f(int my_max, int my_min) : class_f() { }
};