2017年3月5日 星期日

重載複製建構子 與 複製函數 operator=()

重載複製建構子與複製函數

tags: C++ Concept

自動生成

如果你沒有建立這些函式,編譯器會自動幫你建立這些函式
class T {
public:
    T();
    ~T();
    T(const T &);
    T& operator=(const T &);
};

拷貝問題

預設建立的這些函式在某些情況下並不適合,可以用深層拷貝與淺層拷貝的概念來看,預設的僅僅只會做淺層拷貝,複製當前的容器型別。如果你的代碼中存在著指針,可能會出一些問題,預設函式不會拷貝指針所指之處。
就是說當利用預設複製函式複製一個含指針的自訂類別
T a;
T b=a;
當你嘗試修改a資料成員時,由於a與b的資料成員指向同一處,因此會連動造成改a時b也一起被更動了。很多時候這可能不是我們所要的結果。
/*****************************************************************
Name : 
Date : 2017/03/06
By   : CharlotteHonG
Final: 2017/03/06
*****************************************************************/
#include <iostream>
#include <numeric>
using namespace std;

class List{
public:
    // 建構子
    List(size_t len=3): len(len){
        this->list = new int[len];
        // 初始化資源
        iota(list,list+len,1);
    }
    // 解構子
    ~List(){
        delete [] this->list;
    }
public:
    List & pri(string name=""){
        if(name != "")
            cout << name << endl;
        for(unsigned i = 0; i < this->len; ++i) {
            cout << "  " << (*this).list[i] << ", ";
        } cout << endl;
        return (*this);
    }
public:
    int* list;
    size_t len;
};
/*==============================================================*/
int main(int argc, char const *argv[]){
    List a, b=a;
    a.pri("origin");
    b.pri();

    a.list[0]=7;
    a.pri("a[0]=7");
    b.pri();

    return 0;
}
/*==============================================================*/
你會發現僅更改a的數值結果卻連b也一起更動了。

複製建構函式與複製函式差別

建立時賦值與建立後賦值是不一樣的

來看這樣的例子
int i=0;
int i(0);
原始的C語言在宣告時用 = 賦值就是初始化的意思。C++多了一種比較不會產生閱讀含意誤會的方式,避免原本的寫法。
int i=0;
含有
先建立 i 再建立常數 0 ,然後在更改 i 的數值(複製)
這樣的閱讀含意
注意:閱讀含意不代表編譯器真這樣做,僅僅只是依照看起來的樣式去推論,也有不少人忍為 i=0i(0) 閱讀含意沒有區別且前者更容易被閱讀與理解。這沒有什麼影響與爭論純屬個人習慣。

初值陣列

建立時直接給定值(初值陣列),而不是建立後再複製給值
當成員函數具備const修飾時可以明顯看出差異。因為一個已經被建立的const物件是不能夠被修改的,只能在建立的時候就賦予初值。
class T {
public:
    T(int i):i(i){
        cout << "i=" << this->i << endl;
    }
private:
    const int i;
};
你可以試著移除初值陣列,使用事後賦值的方式,會發現無法修改
class T {
public:
    T(int i){
        this->i = i;
        cout << "i=" << this->i << endl;
    }
private:
    const int i;
};
我想試著從這個例子告訴你初值陣列的優點,並從此說明建立時賦值與建立後賦值是兩回事。

建構複製函式

原意就是直接在建立時就給定別人已經建好的規格
T a, a(b);
而不是
T a, T b;
a=b;
你可以想像一個物品在生產工廠就幫你客製化好拿到手直接用比較有效率;還是工廠出來你又自己修改比較有效率呢?
後者在極端的情況下,你會發現你手上多了換下來似乎用不大上的零件(你還多花錢買這換下來,卻暫時完全用不上的零件)。
他的函式長這個樣子
List::List(const List & rhs): len(rhs.len){...}
可以看出呼叫條件就是在宣告時呼叫
List a;
List b(a);
現在你應該可以解為什麼這樣的寫法閱讀含意更清楚了吧
int i(0);
你也可以寫作
List a;
List b=a;
int i=0;
他們是等價的。

operator複製函式

已經生成好了,既有物件間的複製
List a, b;
b=a;
他的函式長這個樣子
List & List::operator=(const List & rhs){...}

重載複製函式

建構子

  • 要求記憶體空間
  • 給定初值

複製建構子

  • 要求記憶體空間
  • 複製資源(或給定出值)

複製函式

  • 要求記憶體空間
  • 複製資源
  • 刪除舊有資源
  • 判定來源是否相同
/*****************************************************************
Name : 
Date : 2017/03/06
By   : CharlotteHonG
Final: 2017/03/06
*****************************************************************/
#include <iostream>
#include <numeric>
using namespace std;

class List{
public:
    // 建構子
    List(size_t len=3): len(len){
        this->list = new int[len];
        // 初始化資源
        iota(list,list+len,1);
    }
    // 複製建構子
    List(const List & rhs): len(rhs.len){
        this->list = new int[len];
        // 複製
        for(unsigned i = 0; i < len; ++i)
            (*this).list[i] = rhs.list[i];
    }
    // 解構子
    ~List(){
        delete [] this->list;
    }
public:
    List & pri(string name=""){
        if(name != "")
            cout << name << endl;
        for(unsigned i = 0; i < this->len; ++i) {
            cout << "  " << (*this).list[i] << ", ";
        } cout << endl;
        return (*this);
    }
public:
    // 重載賦值符號
    List & operator=(const List & rhs){
        // 相同則離開
        if(this == &rhs)
            return (*this);
        // 清除原始資源
        this->~List();
        // 重建資源
        this->list = new int[len];
        this->len = rhs.len;
        // 複製
        for(unsigned i = 0; i < this->len; ++i)
            (*this).list[i] = rhs.list[i];
        return (*this);
    }
public:
    int* list;
    size_t len;
};
/*==============================================================*/
int main(int argc, char const *argv[]){
    List a(2);
    List b=a;
    List c(4);

    a.pri("origin");
    b.pri();
    c.pri();

    c=a;

    a.list[0]=7;
    a.pri("a[0]=7");
    b.pri();
    c.pri();

    return 0;
}
/*==============================================================*/

參考

  1. C++中复制构造函数与重载赋值操作符总结
  2. 複製建構函式、物件的指定
  3. C++ 快速導覽 - 類別 Copy 建構函數

沒有留言:

張貼留言