explicit的意義是讓程式員能制止"單一參數的constructor"被當作conversion運算符 default constructor default constructor只有在被編譯器需要時,才會被合成出來,且合成出的constructor只執行編譯器所需要的行動(並不對成員初始化) ...
- explicit的意義是讓程式員能制止"單一參數的constructor"被當作conversion運算符
default constructor
- default constructor只有在被編譯器需要時,才會被合成出來,且合成出的constructor只執行編譯器所需要的行動(並不對成員初始化)
含有default constructor的member class object
- 在c++各個不同的編譯模塊(文件)中,編譯器將合成的default constructor、copy constructor、destructor、assignment copy operator都用inline方式完成來避免合成多個default constructor
- 若一個class沒有任何constructor,而其內含的member object有default constructor,編譯器則需要為此class合成default constructor
//B內含A
class A {
public:
A();
A(int);
...
}
class B{
public:
B a;
char* str;
}
//由於A有default constructor,而B內含A且B沒有任何constructor,因此編譯器需要為B合成一個default constructor,讓其來調用A的default constructor處理B::a
//此處B中被合成的default constructor樣子
inline
B::B()
{
a.A::A();
}
-
若class B中內含有一個及以上的member class objects,那麼class B中的每一個constructor必須調用每一個member class的default constructor;如果是多個,編譯器安插代碼,會按照member objects在class中聲明順序來調用對應的constructor。因此,編譯器會擴張已存在的constructor,在其中安插必要代碼,使其先調用member class的default constructor
//若我們為B寫一個default constructor讓其初始化str B::B() { str = 0; }; //此時會出現一個問題,那就是B中已經存在一個我們寫的default constructor,編譯器不能為其再合成一個,那麼我們便沒法調用a的constructor。 //此時,編譯器會擴張已存在的constructor,在其中安插必要代碼,使其先調用member class的default constructor //擴張後的B的default constructor內部結構 B::B() { a.A::A(); str = 0; }
含有default constructor 的 base class
- 一個沒有任何constructor的class派生自含有default constructor的base class,編譯器會調用上層的default constructor,為derived class合成default constructor;若base class含有許多constructors,但沒有default constructor,編譯器會擴張每一個constructor,安插必要代碼,以此調用必要的default constructor。因為存在其他constructors,並不會合成default constructor
含有virtual functions的 class
class A
{
public:
virtual void do() = 0;
}
void do( const A& a ) { a.do(); };
void act()
{
//B和C派生自A
B b;
C c;
do( b );
do( c );
}
-
此時編譯器會產生兩個擴張行為:
- virtual function table被編譯器產出,其中存放virtual functions地址
- 每一個class object中,vptr會被編譯器合成,內含與之相關的vtbl地址
-
並且,a.do()的virtual invocation會被改寫
//原本a.do() ( *a.vptr[1] )( &a )
- 1表示do()在vtbl中的固定索引
- &a表示交給被調用的do()實例的this指針
-
為了讓以上機制發揮效果,編譯器必須為base和其derived class object的vpty設定初值,指向相關的virtual table地址。這樣的任務會交給constructor做擴張
含有virtual base class的class
- 由於在編譯期時,virtual base class在每一個derived class object記憶體佈局中位置不能夠確定,因此編譯器需要合成default constructor,在derived class object中的每一個virtual base classes中安插一個指針,以此確定其位置
copy constructor
default memberwise initialization
-
以下三種情況會以一個object的內容作為另一個class object的初值:
-
顯示地以一個object內容作為另一個class object的初值
-
object被當做參數傳給某函數
-
函數傳回class object
//第一種 class A {...}; A a; A aa = a; //第二種 void do1( A a ); void do2() { A aa; foo(aa); } //第三種 A do3() { A a; return a; }
-
-
若一個class object沒有explict copy constructor,而當class object以相同的class的另一個object作為初值時,此class object內部會把每一個內部或derived的data member的值,從另一個object拷貝一份到自己這,再以遞歸的方式進行memberwise initialization,但它並不拷貝member class object
-
memberwise initialization這一機制由bitwise copy semantics和default copy constructor實現,當class不展現bitwise copy semantics,編譯器才會合成default copy constructor
-
位拷貝(淺拷貝/bitwise copy semantics):編譯器只是直接將data member的值或指針的值拷貝過來,並不拷貝member class object;也就是說這會導致多個指針指向同一對象
不展現bitwise copy semantics
- 以下四種情況不會展現bitwise copy semantics:
- class內含member object,而後者的class聲明瞭copy constructor
- class繼承自base class,而base class聲明瞭copy constructor
- class聲明一個及以上virtual functions
- class派生自一個繼承串鏈,其中含有一個及以上virtual base classes
對於第一、第二種情況
-
若class展現了bitwise copy semantics且該class並沒有explict copy constructor,此時編譯器不會合成copy constructor
//以下這種情況,因為展現出了bitwise copy semantics,因此編譯器並不會合成copy constructor class A { public: ...//不包含explict copy constructor A(const char*); ~A(); private: int val; char* str; } A a("example") void do() { A aa = a; } //以下這種情況,因為biewise copy semantics無法調用內部class object的copy constructor,因此編譯器需要合成一個copy constructor來調用 class A { public: A(const String&); ~A(); private: int val; String str; } class String { public: String(const char*); String(const String&); ~String(); }
對於第三、第四種情況
-
對於聲明瞭virtual functions的class,編譯器都會在編譯器為其進行擴張,分別生成一個vptr和一個vtbl。此時用一個新的class類型object賦值給前一個class,bitwise copy semantics不在起作用,不然編譯器設定的vptr並不是正確的,此時編譯器需要合成default copy constructor。然而,對於一樣的class object,bitwise copy semantics依然生效(除開pointer member)
//以下這種情況bitwise copy semantics依然生效,兩個class AA實例的vptr將指向同一vtbl class A { public: A(); ~A(); virtual void do1(); private: //需要的變數 } class AA : public A { public: AA(); void do1() override; virtual do2(); private: //需要的變數 } void test() { AA aa; AA aa1 = aa; } //以下這種情況,兩個類型的class object的vptr將指向各自相關的vtbl A a = aa;
-
一個class object如果以另一個含有virtual base class subobject作為初值,bitwise copy semantics會失效,因為virtual base class subobject的位置不確定,bitwise copy semantics會破壞這個位置,維護這個位置由編譯器完成,因此需要由編譯器合成default copyconstructor做出應對行為
program transformation semantics
explict initialization
有這樣一段代碼:
class X{...}; //定義了copy constructor
X x;
void do()
{
X x1(x);
X x2 = x;
X x3 = X(x);
}
-
上面的初始化操作將進行兩個必要的可能的程式轉化:
- 重寫每一個定義(占用記憶體),初始化操作剝除
- 安插class的copy constructor
//轉化後 //可能的轉換,不同編譯器進行的事兒不同 void do() { X x1; X x2; X x3; x1.X::X(x); x2.X::X(x); x3.X::X(x); }
argument initialization
void do( X x );
X x1;
do(x1);
-
以上代碼進行argument initialization,函數會讓local instance x 用memberwise將x1當作初值
-
而對於不同的編譯器,這一過程有不同的實現方法:
-
導入臨時object,調用copy constructor:
//需要更改argument,不然需要多進行一次bitwise void do( X& x ); //編譯器產生的臨時object X _tempx; _tempx.X::X(x1); do( _tempx );
-
return value initialization
X do()
{
X xx;
...
return xx;
}
-
對於以上函數的返回值object,採用兩步將局部對象xx拷貝而來:
-
為函數加一個一個class object 的reference的額外參數,用來放置copy construct的返回值
-
在return前安插copy constructor調用操作,將傳回的object的內容作新增參數的初值
-
這種方法又被稱為named return value(NRV)優化
void do( X& _result ) { X xx; xx.X::X(); _result.X::X( xx ); return; } //do()的操作調用也隨之改變 X x1 = do(); //轉為 X x1; do(x1);
-
-
NRV優化的缺點:
- 優化由編譯器進行,因此這種優化是否完成,我們並不知道
- 隨著函數變得越來越複雜,優化會越來越難施行
是否需要copy constructor?
-
對於沒有member或base class objects帶有copy constructor,以及沒有virtual base class 或 virtual function的class,並不需要copy constructor,此時memberwise即可滿足要求,效率即可也安全,並不會導致memory leak,也沒有address aliasing。再者,對於這類class,使用memcpy()會更有效率
class point3d { public: point3d( float x, float y, float z ); private: float _x, _y, _z; } //此時class的copy constructor這樣執行更有效率 point3d::point3d( const point3d& rhs ) { memcpy( this, &rhs, sizeof(point3d) ); };
member initialization list
-
member initialization list不是一組函數調用,編譯器安照member在class中的聲明順序一一操作member initialization list,且在任何user code前,以適當順序在constructor中安插初始化操作
-
對於以下四種情況,為保證程式順利編譯,必須使用member initialization list:
- 初始化reference member
- 初始化const member
- 調用base class 的constructor,而它含有一組參數
- 調用member class的constructor,而它含有一組參數
-
以下情況編譯可以通過,但效率並不高
class A()
{
String _name;
int _value;
public:
A()
{
_name = 0;
_value = 0;
}
}
//此時A constructor會產生一個臨時object,再將其初始化,隨後以assignment運算符將臨時Object傳遞給_name,最後摧毀臨時object
//以下是擴張
A::A()
{
_name.String::String();
String temp = String(0);
//memberwise拷貝
_name.String::operator=(temp);
temp.String::~String();
_value = 0;
}
-
對此進行修改
//運用member initialization list A::A : _name(0) { _cnt = 0; } //如下擴張 A::A() { _name.String::String(0); _value = 0; }
-
若不註意初始化順序和member initialization list的排列順序,可能會出現以下錯誤:
class B { int i; int j; public: B(int val) : j(val), i(j) { } }; //執行順序 i = j; j = val;
-
以上程式改善:將一個member的初始化操作放在constructor
B::B(int val) : j(val) { i = j; }
-
對於member function,請不要使用member initialization list將member function作為另一個對象的初值,因為並不知道member function對class object依賴性,因此需要將其放於constructor