構造函數語義學
--了解編譯器在構造對象時背著我們干了什么勾當
Default Ctor 在需要的時候被構建出來~
什么需要? 是編譯器需要而不是程序的需要,所以不要期望編譯器生成的Ctor會幫我們把我們的成員變量初始化為零。那是程序的需要,也就是我們程序員的任務····
例如:
class Foo(public:int val;);
void foo_bar(){Foo bar; if(bar.val)dosth;} 不要期望編譯器幫我們初始化它為0。只有global變量會初始化為0,初始化val這樣的變量需要我們自己寫代碼的~~
Default Ctor分為trival(沒用的)和non-trival的,下面討論什么時候編譯器需要ctor 也就是有用的ctor,這時候如果我們沒有提供一個默認的default ctor 它會幫我們合成一個的~~
1.帶有Default Ctor 的member class object
很好理解,既然內部成員含有default ctor 那么我們創建新的對象時需要調用它,而我們并木有調用它的函數,編譯器自然會幫我們提供一個。如果我們提供了default ctor ,那么編譯器會在我們提供的函數加入調用必須調用的member class 的default ctor。同樣的在每一個ctor里面都將在用戶寫的代碼之前調用需要調用的default ctor -------看例子:
class Dopey();class Sneezy{public:Sneezy(int val);Sneezy();};class Bashful{public:BashFul();};
class Snow_white{public: Dopey dopey;Sneezy sneezy;Bashful bashful;private:int muble};
如果Snow_white沒有定義default ctor 那么編譯器會創建一個,并在其中依照聲明順序依次調用dopey sneezy bashful的default ctor 然而如果:
Snow_white::Snow_white():sneezy(1024){muble=2045;}
編譯器會擴張為
Snow_white::Snow_white():sneezy(1024){
dopey.Dopey::Dopey();
sneezy.Sneezy::Sneezy(1024);
bashful.Bashful::Bashful();
///////// explicit user code
muble = 2048;
}
2.派生自帶有Default ctor 的 base class
同樣道理如果父類有Default ctor 子類當然要調用,編譯器會為想上面一樣為我們加上
3.含有virtual functions的Class
創建object 時候需要ctor 來設置好vptr
4.帶有virtual base class
virtual base 實現方法各有不同,然而共同點是必須是virtual base class 在其每一個derived class中的位置能夠與執行期準備妥當
X A B C 菱形繼承
void foo(const A*pa){pa->i=1024;}
foo(new A);foo(new C);
知道pa 后 i在對象中的位置并不是固定的,而是在運行時真正確定pa指向什么對象A還是C才能確定的,因此需要設定一個指向基類subobject的指針,所以需要ctor工作了
OK:
1.任何class 如果沒有定義Default ctor 就會被合成一個
2.合成出來的Default ctor 會明確設定每一個data member值
錯的很easy了~
-------------------------------------------------------------------------------
再來看 Copy Ctor:
copy ctor 負責用另一個對象初始化一個對象
operator = 負責用另一個對象給一個對象賦值
直接賦值時,傳參時,返回時可能調用Copy ctor
Default member initialization~~~
也就是memberwise 的initialization
他會把每一個data member (內建的或者派生的)從一個object 拷貝到另一個object中去
如果object允許bitwise的拷貝那么編譯器就不用生成一個nontrival的default copy ctor
什么時候不可以呢~
1 內含一個member object 而后者含有copy constructor (聲明或者合成的)
2 繼承一個base class 后者有copy ctor
3 含有virtual func
4 派生自一個繼承鏈,其中有virtual base class
1和2 中編譯器會把member 或者baseclass copy ctor 調用安插在合成的copy ctor 中
3 中:
如果兩個同樣類型的object賦值時,沒有問題因為他們的vptr相同
但是考慮子類賦值給父類,此時vptr需要更改,那么此時不具有bitwise特性,因此需要編譯器來加入語句正確更新vptr
4中:
每個編譯器都承諾必須讓derived class 中的virtual base class object 在執行期間準備妥當,維護位置完整性是編譯器的責任。bitwise copy 有可能會破壞這個位置所以編譯器需要在自己合成的copy ctor 中作出仲裁
同樣問題發生在繼承體系中子類向父類賦值時,由于對象模型問題,直接bitwise復制可能會導致base class object 的破壞(后面章節會有討論)
--------------------------------------------------------------------------------
程序轉化語義學:
X x0;
void foo(){X x1(x0); X x2=x0; X x3=X(x0);}
轉化:重寫定義,初始化操作會被剝除 copy constructor 調用會被安插
void foo(){ X x1;X x2; X x3; x1.X::X(x0); x2.X::X(x0); x3.X::X(x0);}
參數的初始化:
一種策略導入暫時的object
void foo(X x0);X xx; foo(xx);
轉化:
X _tmp;
_tmp.X::X(x0); foo(_tmp);
foo變成 void foo(X& x0);
另一種是拷貝構建:
把實際參數直接建構造應該在的位置上,函數返回時局部對象的destructor 會執行
也就是說把x0建構在foo 的函數執行的地方
返回值的初始化:
X bar(){
X xx;
return xx;
}
返回值如何從局部對象xx拷貝而來呢?
一種方法:1.加上額外參數,類型是class object的reference,這個參數用來放置被拷貝建構的返回值 (注意拷貝建構也就是說他被放在應該在的位置,也就是說不是局部變量了)
2.return 指令之前安插一個copy constructor 調用操作 ,以便將傳回的object 內容當做上述參數的初值
so 上面的程序變成了:
void bar(X& _result){
X xx;
xx.X::X(); //編譯器產生的default ctor 調用
_result.X::X(xx);//編譯器產生的copy ctor 調用
return ;
}
現在編譯器必須轉換bar的調用操作 X xx=bar();轉換成 X xx; bar(xx); // 注意不用copy ctor了直接操作在xx上了 如果編譯器做了優化 這就是named return value 優化
而:
bar.memfunc();//bar()傳回的X 調用成員函數
變成:
X _tmp; (bar(_tmp),_tmp).memfunc();
同樣道理函數指針 X(*pf)(); 變成 void (*pf)(X&);
使用者的優化:
X bar(const T& y,const T& z){
X xx;
通過 y z 計算xx
return xx;
}
這種情況下要iuxx被memberwise拷貝到編譯器產生的_result中,
如果定義 ctor來利用yz計算xx則:
X bar(const T& y,const T& z){
return X(y,z);
}
變成:
bar(X& result){
result . X::X(y,z);
return;
}
無需copy ctor了
編譯器的優化:
如上所述bar中返回了具名數值 named value,因此編譯器有可能自己優化,方法是以result取代named return value
bar(X& result){_result.X::X(); 直接處理result 并不處理變量xx然后復制給result 這樣就優化了}
這個優化的激活需要class提供一個copy ctor~~~~~~
Copy ctor 要不要:
如果一個class 符合bitwise的要求那么此時member wise 的拷貝已經高效簡潔 無需加入了
但是如果class需要大量的member wise 初始化操作,如用傳值方式返回objects,如果是這樣提供一個copy ctor 可以激活nRV named return value 優化,這就很合理了
成員們的初始化過程:
什么時候必須用初始化列表:
1.初始化一個reference時 2.初始化一個const member 3.調用父類ctor而且有參數時4調用member class ctor 有參數
其他情況呢:
class word{string name;int cnt;public: name=0;cnt=0;}
編譯器可能這么做:
word::word{
name.String::string();調用string的default ctor
string tmp=string(0);
_name.string::operator=(tmp);
tmp.string::~string();
cnt=0;
}
顯然name放到初始化列表會更有效率 ,會變成
name.String::String(0);
而cnt這種內建類型則沒有關系,放不放到初始化列表沒有效率上的差別
初始化列表究竟讓編譯器做了什么????
編譯器會一個個操作list中的式子,以適當次序在ctor內安插初始化操作在任何explicit user code 之前。
注意的地方:
list中的次序是按照members聲明次序決定而不是list 中的排列順序決定。
例如:class x{int i;int j; X(int val):j(val),i(j)}
錯了 i先聲明則i首先賦予val 然后用未初始化的j賦給i。。。
可以這樣X::X(int val):j(val){i=j;}
由于會安插在explicit code 之前 所以沒問題 會變成 j=val; i=j;
可否用member functions 初始化成員??
答案是可以的,因為和objects相關的this指針已經構建妥當,只是要注意函數調用的member是否已經構建妥當了即可
------ - - - ---------- -- --疲憊的結束線- - - - - -- - -----
name return value TEST:
~~~~1 cl /od 不開優化
#include <iostream>
using namespace std;
class RVO
{
public:
RVO(){printf("I am in constructor\n");}
RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
~RVO(){printf ("I am in destructor\n");}
int mem_var;
};
RVO MyMethod (int i)
{
RVO rvo1;
rvo1.mem_var = i;
return (rvo1);
}
int main()
{
RVO rvo;rvo=MyMethod(5);
}
輸出:
I am in constructor //rvo 創建
I am in constructor // rvo1創建
I am in copy constructor // rvo1賦值給hiddern
I am in destructor // rvo1解構
I am in destructor // hiddern解構
I am in destructor // rvo 解構
A MyMethod (A &_hiddenArg, B &var)
{
A retVal;
retVal.A::A(); // constructor for retVal
retVal.member = var.value + bar(var);
_hiddenArg.A::A(retVal); // the copy constructor for A
return;
retVal.A::~A(); // destructor for retVal
}
A MyMethod(A &_hiddenArg, B &var)
{
_hiddenArg.A::A();
_hiddenArg.member = var.value + bar(var);
Return
}
~~~~2 cl /o2 代碼同上
output
I am in constructor //rvo創建
I am in constructor //hiddern 創建
I am in destructor //hiddern 解構
I am in destructor //rvo解構
我不明白的是hiddern 怎么傳給rvo ,我猜可能是編譯器按照bitwise的復制方式進行的,此時編譯器并沒有直接建構結果于rvo上 ,看看下面的試驗:
注:明白了, 結果直接建構在hiddern,然后通過operator = 傳給rvo 。沒有copy ctor因為拷貝構造函數是負責初始化的,而operator = 才是用來賦值的.
經過代碼證明是對的,如果重載賦值運算符 輸出變成:
I am in constructor //rvo創建
I am in constructor //hiddern 創建
I am in operator = //賦值操作~~
I am in destructor //hiddern 解構
I am in destructor //rvo解構
~~~~3 cl /od
#include <iostream>
using namespace std;
class RVO
{
public:
RVO(){printf("I am in constructor\n");}
RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
~RVO(){printf ("I am in destructor\n");}
int mem_var;
};
RVO MyMethod (int i)
{
RVO rvo1;
rvo1.mem_var = i;
return (rvo1);
}
void abc(RVO& i){
}
int main()
{
RVO rvo=MyMethod(5); //此時定義和賦值放到了一個表達式子
return 0;
}
output:
I am in constructor // rvo1 創建 注意 跟上面的第一種情況下的hiddern一樣 rvo并沒有調用ctor
I am in copy constructor // rvo1 拷貝構造給rvo 此時沒有hiddern了 直接構建rvo了
I am in destructor // rvo1 析構
I am in destructor // rvo1 解構
~~~~3 cl /o2 再來~~~~ NRV出馬
I am in constructor // rvo構建了
I am in destructor // rvo析構了
此時 mymethod中的一切都直接反映在rvo身上
ok~~~~~4個代碼完全一樣構造析構拷貝函數個數由2-6不等~~~over~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~