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

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