What is The Rule of Three?

首先擺明問題:

1、拷貝一個對象是什麼意思?

2、拷貝構造函數和拷貝賦值運算符又都是什麼?

3、在什麼時候我需要聲明他們?

4、我如何阻止我的對象被拷貝?


1、前言(Introduction)

C++看待用戶定義的數據變量 with  value semantics,這就意味着在不同的上下文中,對象都被拷貝。因此我們需要理解“copying an object” 到底意味着什麼?


先看一個簡單的例子:

class person
{
    std::string name;
    int age;

public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
};

int main()
{
    person a("Bjarne Stroustrup", 60);
    person b(a);   // What happens here?
    b = a;         // And here?
}

(如果你疑惑 name(name), age(age),這一部分被稱爲成員初始化列表

2、特殊成員功能(Special member functions)

拷貝一個person對象意味着什麼呢?在main函數中聲明瞭兩種不同的場景。

person b(a) 是執行拷貝構造函數。他的工作是基於一個存在的對象,創建一個新的對象。

b=a 是執行拷貝賦值運算符。他的工作稍微要複雜些,因爲目標對象已經是一個處於需要處理的有效狀態

默認的拷貝一個對象就意味着拷貝他的成員。

3、隱式定義(

Implicit definitions

// 1. copy constructor
    person(const person& that) : name(that.name), age(that.age)
    {
    }

    // 2. copy assignment operator
    person& operator=(const person& that)
    {
        name = that.name;
        age = that.age;
        return *this;
    }

    // 3. destructor
    ~person()
    {
    }

成員被逐一的拷貝,在這種情況下,我們看到的是: name 和 age 被拷貝。因此我們得到一個自治,獨立的person對象。析構函數一直是空的,這也是在這種情況下的處罰,因爲我們在構造函數沒有獲得任何的資源。成員的析構總是在person析構完成後隱式的析構。

4、管理資源(Manager resources)

我們應該在什麼時候定義成員函數?當我們的管理一個資源,也就是類的一個對象用到某個資源的時候。這常常意味着在構造函數需要的資源或者析構函數釋放的資源。

讓我們回到標準C++之前,那時候還沒有std::string,並且程序員熱衷於指針,person的類可能定義如下:

class person
{
    char* name;
    int age;

public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
        name = new char[strlen(the_name) + 1];
        strcpy(name, the_name);
        age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
        delete[] name;
    }
};

甚至今天也有人是這樣的風格,這樣會陷入一個麻煩:“I pushed a person into a vector and now I get crazy memory errors!” 

記住一條:拷貝一個對象意味着拷貝他的成員,但是拷貝name僅僅是拷貝一個指針,而不是他指向的一個特徵數組。這有一些不好的影響。

  1. Changes via a can be observed via b.
  2. Once b is destroyed, a.name is a dangling pointer.
  3. If a is destroyed, deleting the dangling pointer yields undefined behavior.
  4. Since the assignment does not take into account what name pointed to before the assignment, sooner or later you will get memory leaks all over the place.
5、顯式定義(Explicit definitions)

因爲成員逐一拷貝達不到我們要的效果,我們必須定義拷貝構造函數和拷貝賦值運算符

// 1. copy constructor
    person(const person& that)
    {
        name = new char[strlen(that.name) + 1];
        strcpy(name, that.name);
        age = that.age;
    }

    // 2. copy assignment operator
    person& operator=(const person& that)
    {
        if (this != &that)
        {
            delete[] name;
            // This is a dangerous point in the flow of execution!
            // We have temporarily invalidated the class invariants,
            // and the next statement might throw an exception,
            // leaving the object in an invalid state :(
            name = new char[strlen(that.name) + 1];
            strcpy(name, that.name);
            age = that.age;
        }
        return *this;
    }

初始化和賦值的區別:我們必須丟棄之前的狀態,爲了避免內存泄露。

6、the rule of three

If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them.

如果你需要明確的聲明析構函數,拷貝構造函數和拷貝賦值運算符,你可能需要顯式聲明他們三個。


原文地址:http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章