建構函式 (C++)
建構函式是初始化其類別執行個體的特殊成員函式。 若要呼叫建構函式,請使用類別名稱以及用大括號或括號括住的參數。
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class SomeClass{
public:
static void set_box(const Box& aBox) {}
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
SomeClass::set_box(Box{ 5, 6, 7 });
}
如需詳細資訊,請參閱宣告建構函式的規則。 如需初始化的詳細資訊,請參閱初始化。
建構順序
建構函式會依此順序執行其工作:
它會依宣告順序呼叫基底類別和成員建構函式。
如果類別是從虛擬基底類別衍生,它會初始化物件的虛擬基底指標。
如果類別具有或繼承虛擬函式,它會初始化物件的虛擬函式指標。 虛擬函式指標指向類別的虛擬函式表,以便讓虛擬函式呼叫正確繫結至程式碼。
它會執行其函式主體內的任何程式碼。
下列範例顯示在衍生類別的建構函式中呼叫基底類別和成員建構函式的順序。 首先會呼叫基底建構函式,然後依其出現在類別宣告中的順序初始化基底類別成員,最後會呼叫衍生的建構函式。
#include <iostream>
using namespace std;
class Contained1 {
public:
Contained1() {
cout << "Contained1 constructor." << endl;
}
};
class Contained2 {
public:
Contained2() {
cout << "Contained2 constructor." << endl;
}
};
class Contained3 {
public:
Contained3() {
cout << "Contained3 constructor." << endl;
}
};
class BaseContainer {
public:
BaseContainer() {
cout << "BaseContainer constructor." << endl;
}
private:
Contained1 c1;
Contained2 c2;
};
class DerivedContainer : public BaseContainer {
public:
DerivedContainer() : BaseContainer() {
cout << "DerivedContainer constructor." << endl;
}
private:
Contained3 c3;
};
int main() {
DerivedContainer dc;
int x = 3;
}
輸出如下:
Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.
如果建構函式擲回例外狀況,解構順序是建構順序的相反:
在建構函式主體中的程式碼會回溯。
基底類別和成員物件會依宣告的反向順序終結。
如果建構函式為非委派,所有完全建構的基底類別物件和成員都會終結。 不過,因為物件本身未完全建構,所以不會執行解構函式。
明確建構函式
使用建構函式的 explicit 關鍵字,可在建構函式只有一個參數時,或是除了一個以外的所有參數都有預設值時,防止隱含類型轉換。 如需詳細資訊,請參閱建構函式 (C++)。
預設建構函式
「預設建構函式」(Default Constructor) 也就是沒有參數的建構函式,遵循稍微不同的規則。
如果在類別中未宣告建構函式,編譯器會提供預設建構函式:
class Box {
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1{};
Box box2;
}
當您呼叫預設建構函式和嘗試使用括號時,會發出警告:
class myclass{};
int main(){
myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?)
}
這是「最令人惱怒的語法解析」(Most Vexing Parse) 問題範例。 由於範例運算式可解譯為函式的宣告或做為預設建構函式的引動過程,而且由於 C++ 剖析器偏好宣告更勝於其他項目,因此運算式被視為函式宣告。 如需詳細資訊,請參閱最繁瑣的語法解析。
如果已宣告任何非預設建構函式,編譯器不會提供預設建構函式:
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
Box box4; // compiler error C2512: no appropriate default constructor available
}
如果類別沒有預設建構函式,該類別的物件陣列無法透過單獨使用方括號語法來建構。 例如,根據上述程式碼區塊,Boxes 陣列無法宣告如下:
Box boxes[3]; // compiler error C2512: no appropriate default constructor available
不過,您可以使用初始設定式清單集合來初始化 Boxes 陣列:
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
複製和移動建構函式
「複製建構函式」(Copy Constructor) 使用物件的參考,以建立其複本。 如果您未提供類別的複製建構函式,即使已宣告複製作業、移動作業或解構函式,編譯器也會建立預設的複製建構函式。 如需詳細資訊,請參閱宣告建構函式的規則。
「移動建構函式」(Move Constructor) 可讓配置的記憶體從某個物件轉移到另一個物件。 如需詳細資訊,請參閱如何:撰寫移動建構函式。
明確預設和刪除的建構函式
您可以明確預設複製建構函式、預設建構函式、複製指派運算子和解構函式,但不支援明確預設移動建構函式和移動指派運算子。 (Visual Studio 2015 提供這項支援)。 您可以明確地刪除所有特殊函式。 如需詳細資訊,請參閱明確的預設和被刪除的函式。
在衍生類別中的建構函式
衍生類別建構函式一定會呼叫基底類別建構函式,因此,它可以依賴完全建構的基底類別,才進行任何額外的工作。 基底類別建構函式是按照衍生的順序進行呼叫,例如,如果 ClassA 衍生自 ClassB,後者衍生自 ClassC,會先呼叫 ClassC 建構函式,然後呼叫 ClassB 建構函式,最後呼叫 ClassA 建構函式。
如果基底類別沒有預設建構函式,您必須在衍生類別建構函式中提供基底類別建構函式參數:
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){
m_label = label;
}
private:
string m_label;
};
int main(){
const string aLabel = "aLabel";
StorageBox sb(1, 2, 3, aLabel);
}
具有多重繼承之類別的建構函式
如果類別從多個基底類別衍生,基底類別建構函式是依照其列在衍生類別宣告中的順序進行叫用:
#include <iostream>
using namespace std;
class BaseClass1 {
public:
BaseClass1() {
cout << "BaseClass1 constructor." << endl;
}
};
class BaseClass2 {
public:
BaseClass2() {
cout << "BaseClass2 constructor." << endl;
}
};
class BaseClass3{
public:
BaseClass3() {
cout << "BaseClass3 constructor." << endl;
}
};
class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3 {
public:
DerivedClass() {
cout << "DerivedClass constructor." << endl;
}
};
int main() {
DerivedClass dc;
}
可預期下列輸出:
BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.
在建構函式中的虛擬函式
建議您呼叫建構函式中的虛擬函式時要小心。 因為基底類別建構函式一定是在衍生類別建構函式之前叫用,所以在基底建構函式中所呼叫的函式是基底類別版本,而非衍生類別版本。 在下列範例中,建構 DerivedClass 會導致 BaseClass 的 print_it() 實作先執行,然後 DerivedClass 建構函式才會導致 DerivedClass 的 print_it() 實作執行:
#include <iostream>
using namespace std;
class BaseClass{
public:
BaseClass(){
print_it();
}
virtual void print_it() {
cout << "BaseClass print_it" << endl;
}
};
class DerivedClass : public BaseClass {
public:
DerivedClass() {
print_it();
}
virtual void print_it(){
cout << "Derived Class print_it" << endl;
}
};
int main() {
DerivedClass dc;
}
輸出如下:
BaseClass print_it
Derived Class print_it
建構函式和複合類別
包含類別類型成員的類別稱為「複合類別」(Composite Class)。 在建立複合類別的類別類型成員時,會先呼叫建構函式,然後呼叫類別自己的建構函式。 當包含的類別缺少預設建構函式時,您必須在複合類別的建構函式中使用初始設定清單。 在先前的 StorageBox 範例中,如果將 m_label 成員變數的類型變更為新的 Label 類別,您必須呼叫基底類別建構函式和初始化 m_label 建構函式中的 StorageBox 變數:
class Label {
public:
Label(const string& name, const string& address) { m_name = name; m_address = address; }
string m_name;
string m_address;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, Label label)
: Box(width, length, height), m_label(label){}
private:
Label m_label;
};
int main(){
// passing a named Label
Label label1{ "some_name", "some_address" };
StorageBox sb1(1, 2, 3, label1);
// passing a temporary label
StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });
// passing a temporary label as an initializer list
StorageBox sb3(1, 2, 3, {"myname", "myaddress"});
}
委派建構函式
「委派建構函式」(Delegating Constructor) 呼叫相同類別中的其他建構函式,以執行某些初始化工作。 在下列範例中,衍生類別有三個建構函式—第二個建構函式委派至第一個,而第三個建構函式委派至第二個:
#include <iostream>
using namespace std;
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
ConstructorDestructor dc(1, 2, 3);
}
輸出如下:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.
在任何建構函式完成時,建構函式建立的物件會立即完全初始化。 DerivedContainer(int int1) 成功,不過,DerivedContainer(int int1, int int2) 會失敗,並呼叫解構函式。
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
try {
ConstructorDestructor cd{ 1, 2, 3 };
}
catch (const exception& ex){
}
}
輸出:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.
如需詳細資訊,請參閱統一初始設定和委派建構函式。