2017年3月26日 星期日

operator=() 進階重載2 同時實現深度拷貝與淺度拷貝功能

operator=() 進階重載2 同時實現深度拷貝與淺度拷貝功能

tags: operator

補充知識:

如果一個物件成員具有指針,那麼複製時候將存在兩種方式,深度拷貝與淺度拷貝,將兩種拷貝另用其他函式實現在介面使用時並不怎麼好
解決方法,可以利用右值引用來區分等號呼叫的函式,利用此一特性可以區別等號的兩種拷貝方式,而不用額外在重寫
將逐一介紹如實現該功能,以及可能遇到的坑,並提供完整的參考代碼

std::拷貝函式

std提供一個拷貝函式, 會拷貝指針所指之處的值,也就是深度拷貝。
vector<int> v{0, 1, 2}, v2(3);
std::copy(v.begin(), v.begin()+v.size(), v2.begin());
分別需要輸入的是
std::copy(起始指針, 結束指針, 對象起始指針);

重載深度拷貝複製建構函式

深度拷貝的複製建構函式透過 std::copy() 實現
// 複製建構子
List(List const & rhs): len(rhs.len), list(new T[rhs.len]){
    std::copy(rhs.list, rhs.list + len, this->list);
}
需要做的是初始資源空間與長度

重載深度拷貝複製函式

複製函式與複製建構函式的差異於,資源已經事先建立好了,需要先清除本地資源再重建資源,然後再進行賦值。然後也要注意是否會發生將自己複製給自己的情況,不應該浪費效能重新複製。
List & operator=(List const & rhs){
    cout << "copy" << endl;
    // 相同則離開
    if(this != &rhs){
        // 清除本地資源
        this->~List();
        // 重建資源
        this->list = new T[len];
        this->len = rhs.len;
        // 深度拷貝
        std::copy(rhs.list, rhs.list + len, this->list); 
    }
    return (*this);
}

重載移動函式

與複製函式一樣,本地資源已經不需要可以先釋放他,然後再將來源指針複製到本地指針。
需要注意的點是,這樣複製完之後會產生同時兩個指針指向同一個地址,這在釋放時會被釋放兩次,必須將來源指針歸零。
// 移動函式
List & operator=(List && rhs){
    cout << "Move" << endl;
    // 相同則離開
    if(this != &rhs){
        // 清除本地資源
        this->~List();
        // 淺度拷貝
        this->len = rhs.len;
        this->list = rhs.list;
        // 清空來源地址
        rhs.list = nullptr;
        rhs.len = 0;
    }
    return (*this);
}

重載移動建構函式

比較特別的是,由於不需要要求空間,可以直接套用移動函式來完成實作,藉此省去多餘的代碼。
要注意的是這裡也需要使用 std::move() ,否則右值引入的同時,他就具備名字了,會變成左值。
// 移動建構子
List(List && rhs): len(0), list(nullptr){
    (*this) = std::move(rhs);
}

範例代碼

/*****************************************************************
Name : operator 移動語意的實現
Date : 2017/03/25
By   : CharlotteHonG
Final: 2017/04/07
*****************************************************************/
#include <iostream>
#include <iomanip>
#include <numeric>
#include <vector>
#include <initializer_list>
#include <algorithm>
using namespace std;

template <typename T>
class List{
public:
    // 建構子
    List(initializer_list<T> n): len(n.size()), list(new T[len]){
        std::copy(n.begin(), n.end(), this->list);
    }
    // 複製建構子
    List(List const & rhs): len(rhs.len), list(new T[rhs.len]){
        std::copy_n(rhs.list, len, this->list);
    }
    // 移動建構子
    List(List && rhs): len(0), list(nullptr){
        (*this) = std::move(rhs);
    }
    // 解構子
    ~List(){
        if(this->list != nullptr) {
            delete [] this->list;
        }
    }
public:
    List & pri(string name=""){
        if(name != "")
            cout << name << endl;
        for(unsigned i = 0; i < this->len; ++i) {
            cout << setw(3) << (*this).list[i];
        } cout << endl;
        return (*this);
    }
public:
    // 複製函式
    List & operator=(List const & rhs){
        cout << "copy" << endl;
        // 相同則離開
        if(this != &rhs){
            // 清除本地資源
            this->~List();
            // 重建資源
            this->list = new T[len];
            this->len = rhs.len;
            // 深度拷貝
            std::copy_n(rhs.list, len, this->list); 
        }
        return (*this);
    }
    // 移動函式
    List & operator=(List && rhs){
        cout << "Move" << endl;
        // 相同則離開
        if(this != &rhs){
            // 清除本地資源
            this->~List();
            // 淺度拷貝
            this->len = rhs.len;
            this->list = rhs.list;
            // 清空來源地址
            rhs.list = nullptr;
            rhs.len = 0;
        }
        return (*this);
    }
public:
    size_t len;
    T* list;
};
/*==============================================================*/
int main(int argc, char const *argv[]){
    List<int> a{3, 2, 1};
    List<int> b{1, 2, 3};
    a.pri();
    // 移動函式
    a = move(b);
    a.pri();
    // 移動建構函式
    List<int> c = std::move(a);
    c.pri();
    return 0;
}
/*==============================================================*/

沒有留言:

張貼留言