2012年4月4日 星期三

[筆記] 複製建構式 copy-constructor

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 複製建構式 copy-constructor
 時間  2012/04/04 Wed 17:31:47
───────────────────────────────────────

不負責任聲明:以下憑印象+google寫成,有錯誤很正常 0rz

              如果有人願意指出錯誤,我會很感謝您的 <(_ _)>

--------------------本文開始分隔線--------------------

複製建構式:
A::A(const A &a )
{

}

前一篇文章有提到,C++標準中有幾個函式你不需要寫,C++都會幫你生出來,

而你寫了,就不會幫你產生,複製建構式是其中一個


那什麼時候會需要自己寫一個?

Ans:當有使用指標的時候,不然 copy 的會是 pointer,而不是 data

1. http://www.cnblogs.com/oomusou/archive/2007/01/14/620374.html

當類別資料copy涉及記憶體時,永遠要寫複製建構式與operator =();

複製建構式: A::A(const A &a ){}

指定運算元: A::operator =( const A& a ){}


不然在解構時,同一個運算元會被解構兩次而造成 code 掛掉

2. http://imandrewliao.blogspot.com/2009/01/blog-post_22.html

(第三點的內容)


另外印象中好像有提到在使用 copy-constructor 時

會配合使用 swap function 來做資料的交換

(不過我不知道原因為何…0rz)


如交換 a, b 資料 → a.swap(b)

temp(b)
this → swap(b)
return *this;


另外
    (a = b).function;

    a = b;
    a.function

是相同的

--------------------------------------------------

以下是第一個連結的全文轉貼,避免連結失效

(原創) 哪些地方會用到Copy Constructor和Assignment Operator? (C/C++)

C#、Java都沒有copy constructor,所以這對大部分programmer都很陌生

簡單地說,凡需要copy的地方,就需要copy constructor:


1.由copy-initialization方式建立物件。

    Ex.

    Foo foo1;
    Foo foo2(foo1);

    以上直接使用copy constructor。

    string s = "C++ Primer";
    Foo foo = Foo();

    此時是先由default constructor建立一個temporary object後,

    再由copy constructor將temporary object 『copy』給物件。

2.以by value的方式傳進function和由function return值。

    Ex.
    int Foo(int n);

    只要不是使用by reference的方式,就得使用copy constructor。

3.建立STL container中的element。

    Ex.vector<string> svec(5);

    先由string的default constructor建立一個temporary string object,

    再由string的copy constructor『copy』到vector中每個element。

4.由initialization list建立array。

    Ex.int ia[] = {1, 2, 3};

    先由int default contructor先建立temporary int object,

    再由int的copy contructor『copy』到array中。

    所以copy constructor其實是無所不在的,只是我們從來沒有發現,

    雖然C++ compiler會自己synthesize copy constructor,

    但也允許我們自己定義自己的Copy Constructor,這與C#、Java不同。


而assignment operator呢?

Foo foo1;
Foo foo2;
foo2 = foo1;

以上會執行foo2的assignment operator。


所以簡單的說,copy constructor和assignment operator都在做『copy』的動作,

資料是pointer,也就是動態資料時,就必須重新改寫,

否則只會copy pointer,而不是copy data。

--------------------------------------------------

以下是第二個連結的全文轉貼,避免連結失效


建構式與解構式

預設建構式與解構式有許多的地雷,首先,如先前所說,

C++在你沒有撰寫此兩個函式的時候,會自動生成,

那麼因為建構式支援多載,預設解構式還會生成嗎?

答案是不會的,以下以範例說明:

class ClassHasDefaultConstruct
{

};

class ClassHasNoDefaultConstruct
{
    public:
        ClassHasNoDefaultConstruct(int i){}
};


我們可以注意到第二個範例,有寫建構式但不是預設建構式(沒有任何參數的),

此時C++就放棄幫你建立預設建構式了,

可是有時候,我們在使用STL函式庫的時候,強迫要求你設計預設建構式,

所以ClassHasNoDefaultConstruct這個類別是不能為STL容器所用。


這裡有個小伎倆,把有參數的建構式當成預設建構式用,利用預設參數的方式:

class ClassHasNoDefaultConstruct2
{
    public:
        ClassHasNoDefaultConstruct2(int i=0){}
};

使用建構式有一些注意事項,如果你的類別支援繼承,在建構式中,不可呼叫虛擬函式,

因為,物件建構的順序是:基底類別建構式完成後,然後才是衍生類別建構,

所以在基底類別建構式中呼叫虛擬函式,永遠是基底類別的函式



其次養成習慣,最好在建構式中,給予所有變數初值,避免執行期間產生錯誤,

因為一般而言,

C++在除錯版的時候,會自動給定變數初值(0, NULL),

而Release版不會,

變數值是看當時配置的記憶體資料內容而定,

所以典型的程式描述是我Debug版跑起來都沒錯,但是Release版就是會當機...。

class BadSample
{
    private:
        char *ptr;

    public:
        BadSample(){};
        ~BadSample()
{

if( ptr ) delete ptr; // 當機
};

};


第三是當類別資料copy涉及記憶體時,永遠要寫複製建構式與operator =();

或者,類別不允許這兩個函式的執行,

原因很簡單,以下以程式說明之:

class CopyConstructor
{
    private:
        char *ptr;

    public:
        void ANewFunction()
        {
            ptr=new char[1024];

        }

        ~CopyConstructor()
        {
            if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
        }
};


以下例子解構時都會當機,因為ptr會被釋放多次:

CopyConstructor a;
a.ANewFunction();

CopyConstructor b(a); // b物件刪除的時候,ptr釋放一次,等到a物件刪除時,又一次

CopyConstructor c;
c=a;  // 同上


所以要嗎你把類別這麼寫:

class CopyConstructor
{
    private:
        char *ptr;

    public:
        void ANewFunction()
        {
            ptr=new char[1024];
        }

        CopyConstructor(const CopyConstructor& c )
        {   
            ptr=new char[1024]; // 各自配置各自的記憶體
            memcpy( ptr, c.ptr, 1024 );
        }

        void operator =(const CopyConstructor& c ) // 簡化起見,不討論回傳值
        {   
            ptr=new char[1024]; // 各自配置各自的記憶體
            memcpy( ptr, c.ptr, 1024 );
        }

        ~CopyConstructor()
        {
            if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
        }
};


要嗎你把類別這麼寫:

class CopyConstructor
{
    private:
    char *ptr;

    public:
        void ANewFunction()
        {
            ptr=new char[1024];
        }

        CopyConstructor(const CopyConstructor& c )
        {   
            ASSERT(0);  //給他當機
        }

        void operator =(const CopyConstructor& c ) // 簡化起見,不討論回傳值
        {
            ASSERT(0);  //給他當機
        }

        ~CopyConstructor()
        {
            if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
        }
};


解構式的注意事項是,如果想讓後續類別繼承,

一定要用virtual,讓繼承解構的順序正確,

還有,同建構式般,不可以在解構式中,呼叫虛擬函式,

因為解構是繼承物件先解構,然後才基底物件,

衍生物件已經不見了,基底物件去哪呼叫正確的虛擬函式呢?

--

     身高不是距離
                   技巧不是問題
                                 只要有"心"
                                             人人都可 定‧三‧米


--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表
▋※ Modify: 2012/04/04 Wed 17:39:15

沒有留言:

張貼留言