1. 程式人生 > >C++拷貝建構函式和operator=

C++拷貝建構函式和operator=

1 拷貝建構函式引數的特點
對於一個類X,如果一個建構函式的第一個引數是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
因此 X::X(X&, int=1); //是拷貝建構函式
並且類中可以存在超過一個拷貝建構函式。拷貝建構函式採用引用作引數原因:
1) 效率 。
2) 避免死迴圈。 當一個物件需要以值方式傳遞時,編譯器會生成程式碼呼叫它的拷貝建構函式以生成一個複本,因此當使用拷貝建構函式時會造成死迴圈。


2 預設拷貝建構函式
如果一個類中沒有定義拷貝建構函式,那麼編譯器會自動產生一個預設的拷貝建構函式。這個預設的引數可能為X::X(const X&)或X::X(X&),由編譯器根據上下文決定選擇哪一個。預設拷貝建構函式的行為如下(遞迴的)(預設的建構函式X()、預設的拷貝賦值函式X& operator=(X& a) 被呼叫時行為同理):
首先呼叫父類拷貝建構函式。然後拷貝建構函式對類中每一個數據成員執行成員拷貝的動作:
a) 如果資料成員為某一個類的例項,那麼呼叫此類的拷貝建構函式。
b) 如果資料成員是一個數組(或指標、引用),對陣列的每一個執行按位拷貝。
c) 如果資料成員是一個數量,如int,double,那麼呼叫系統內建的賦值運算子對其進行賦值。
注:如果父類或成員的類提供的拷貝建構函式為private,則不能通過編譯。這一點對預設的建構函式和預設的拷貝賦值函式被呼叫時同樣適用,因此最好自己定義這三個函式和解構函式。


3 拷貝建構函式呼叫時機
以下情況都會呼叫拷貝建構函式:
1) 一個物件以值傳遞的方式傳入函式體。
2) 一個物件以值傳遞的方式從函式返回。
3) 一個物件需要通過另外一個物件進行初始化。

4 理解copy constructor和copy assignment的一個例子
class callwitch{
      string name ;
      static int objcount;
      public :
             callwitch(const string& na = "") : name(na) {
                   objcount ++;
                   print("callwitch(const string&)");
             };
             ~callwitch(){
                   objcount --;
                   print("~callwitch()");   
             }
             callwitch(const callwitch& obj):name(obj.name) {
                   name = "copy of " + name;
                   objcount ++;
                   print("copy constructor");
             };
             callwitch& operator=(callwitch& obj){
                   print("copy assignment");
             }
             void print(const string& msg = "")const {
                  if(msg.size() !=0)
                      cout << msg << endl ;
               cout << '/t' << name << ": " << "objcount = " << objcount << endl;
             }
};
int callwitch::objcount = 0 ;

callwitch f(callwitch obj)
{
   cout << "returning from f()" << endl;
   return obj ;
}
int main(int argc, char *argv[])
{
    callwitch c1("c1");
callwitch c2 = c1 ; //copy constructor called。因為此時c2還未被初始化成為物件(即還未構造出來),無法呼叫copy assignment函式!
    callwitch c4 ;
    c4 = c1 ;               //copy assignment called
    c2.print("call f()");
    callwitch c3 = f(c1);
    cout << "call f(),no need return value" << endl;
    f(c1);
    system("PAUSE");
    return EXIT_SUCCESS;
}
以下是執行結果:
callwitch(int)
        c1: objcount = 1
copy constructor
        copy of c1: objcount = 2
callwitch(int)
        : objcount = 3
copy assignment
        : objcount = 3
call f()
        copy of c1: objcount = 3
copy constructor
        copy of c1: objcount = 4
returning from f()
copy constructor
        copy of copy of c1: objcount = 5
~callwitch()
        copy of c1: objcount = 4
call f(),no need return value
copy constructor
        copy of c1: objcount = 5
returning from f()
copy constructor
        copy of copy of c1: objcount = 6
~callwitch()
        copy of copy of c1: objcount = 5
~callwitch()
        copy of c1: objcount = 4


5 其它:
1)  最好自定義拷貝建構函式。如果使用編譯器生成的拷貝建構函式,呼叫拷貝建構函式時實行位拷貝,當類內成員變數需要動態開闢堆記憶體時,執行classA obj1 ; classA obj2(obj1)。如果obj1中有一個成員變數指標已經申請了記憶體,那obj2中的那個成員變數也指向同一塊記憶體。這就出現了問題:當obj1把記憶體釋放後,obj2內的指標就是無效指標了,出現執行錯誤。
2) 最好自定義operator=。如果打算在一個內含reference成員、內含const成員時,必須自己定義copy assignment操作符,因為C++本身不允許引用改指不同的物件,也不允許更改const成員;如果base class將copy assignment操作宣告為private,編譯器同樣拒絕為其derived class生成一個copy assignment操作符(effective C++ Item 6)。