21

为了正确处理对象复制,经验法则是三法则。在 C++11 中,移动语义是一回事,所以它是五法则。但是,在这里和互联网上的讨论中,我还看到了对四规则(半)的引用,它是五规则和复制和交换成语的组合。

那么究竟什么是四法则(半法则)?需要实现哪些功能,每个功能的主体应该是什么样的?哪个功能是一半?与五法则相比,这种方法有什么缺点或警告吗?

这是一个类似于我当前代码的参考实现。如果这是不正确的,正确的实现会是什么样子?

//I understand that in this example, I could just use `std::unique_ptr`.
//Just assume it's a more complex resource.
#include <utility>

class Foo {
public:
    //We must have a default constructor so we can swap during copy construction.
    //It need not be useful, but it should be swappable and deconstructable.
    //It can be private, if it's not truly a valid state for the object.
    Foo() : resource(nullptr) {}

    //Normal constructor, acquire resource
    Foo(int value) : resource(new int(value)) {}

    //Copy constructor
    Foo(Foo const& other) {
        //Copy the resource here.
        resource = new int(*other.resource);
    }

    //Move constructor
    //Delegates to default constructor to put us in safe state.
    Foo(Foo&& other) : Foo() {
        swap(other);
    }

    //Assignment
    Foo& operator=(Foo other) {
        swap(other);
        return *this;
    }

    //Destructor
    ~Foo() {
        //Free the resource here.
        //We must handle the default state that can appear from the copy ctor.
        //(The if is not technically needed here. `delete nullptr` is safe.)
        if (resource != nullptr) delete resource;
    }

    //Swap
    void swap(Foo& other) {
        using std::swap;

        //Swap the resource between instances here.
        swap(resource, other.resource);
    }

    //Swap for ADL
    friend void swap(Foo& left, Foo& right) {
        left.swap(right);
    }

private:
    int* resource;
};
4

3 回答 3

16

那么究竟什么是四法则(半法则)?

“四大(半)规则”指出,如果您实施其中之一

  • 复制构造函数
  • 赋值运算符
  • 移动构造函数
  • 析构函数
  • 交换功能

那么你必须有一个关于其他人的政策。

需要实现哪些功能,每个功能的主体应该是什么样子?

  • 默认构造函数(可能是私有的)
  • 复制构造函数(这里有真实的代码来处理你的资源)
  • 移动构造函数(使用默认构造函数和交换):

    S(S&& s) : S{} { swap(*this, s); }
    
  • 赋值运算符(使用构造函数和交换)

    S& operator=(S s) { swap(*this, s); }
    
  • 析构函数(资源的深拷贝)

  • 朋友交换(没有默认实现:/您可能应该想要交换每个成员)。与交换成员方法相反,这一点很重要:std::swap使用移动(或复制)构造函数,这将导致无限递归。

哪个功能是一半?

来自上一篇文章:

“要实现 Copy-Swap 习语,您的资源管理类还必须实现一个 swap() 函数来执行逐个成员的交换(有您的“……(半个)”)”

所以swap方法。

与五法则相比,这种方法有什么缺点或警告吗?

我已经写的警告即将写正确的交换以避免无限递归。

于 2017-08-18T18:02:46.980 回答
6

与五法则相比,这种方法有什么缺点或警告吗?

虽然它可以节省代码重复,但使用复制和交换只会导致更糟糕的类,直言不讳。您正在损害班级的表现,包括移动分配(如果您使用统一分配运算符,我也不喜欢),这应该非常快。作为交换,您将获得强大的异常保证,这起初看起来不错。问题是,您可以使用简单的泛型函数从任何类中获得强大的异常保证:

template <class T>
void copy_and_swap(T& target, T source) {
    using std::swap;
    swap(target, std::move(source));
}

就是这样。所以需要强异常安全的人无论如何都可以得到它。坦率地说,强大的异常安全性无论如何都是一个利基市场。

节省代码重复的真正方法是通过零规则:选择成员变量,这样您就不需要编写任何特殊函数。在现实生活中,我会说 90+ % 的时间我看到特殊的成员函数,它们很容易被避免。即使您的类确实具有特殊成员函数所需的某种特殊逻辑,通常最好将其向下推成为会员。您的记录器类可能需要在其析构函数中刷新缓冲区,但这不是编写析构函数的理由:编写一个处理刷新的小型缓冲区类并将其作为记录器的成员。记录器可能具有可以自动处理的各种其他资源,并且您希望让编译器自动生成复制/移动/破坏代码。

关于 C++ 的事情是自动生成特殊函数是全部或全部,每个函数。那就是复制构造函数(例如)要么自动生成,考虑到所有成员,要么您必须手动编写(更糟糕的是,维护)。所以它强烈地推​​动你采取一种向下推动事物的方法。

如果您正在编写一个类来管理资源并需要处理这个问题,它通常应该是:a)相对较小,b)相对通用/可重用。前者意味着一些重复的代码没什么大不了的,后者意味着您可能不想将性能留在桌面上。

总之,我强烈反对使用复制和交换,以及使用统一赋值运算符。尝试遵循零法则,如果不能,遵循五法则。仅当您可以使其比通用交换(执行 3 次移动)更快时才编写swap,但通常您不必费心。

于 2017-08-18T18:43:44.317 回答
0

简单来说,只要记住这一点。

0 规则

Classes have neither custom destructors, copy/move constructors or copy/move assignment operators.

规则 3:如果您实现了其中任何一个的自定义版本,那么您就实现了所有这些。

Destructor, Copy constructor, copy assignment

5 规则:如果您实现自定义移动构造函数或移动赋值运算符,则需要定义所有 5 个。需要移动语义。

Destructor, Copy constructor, copy assignment, move constructor, move assignment

四个半规则:与规则 5 相同,但具有复制和交换习语。通过包含交换方法,复制赋值和移动赋值合并为一个赋值运算符。

Destructor, Copy constructor, move constructor, assignment, swap (the half part)

Destructor: ~Class();
Copy constructor: Class(Class &);
Move constructor: Class(Class &&);
Assignment: Class & operator = (Class);
Swap: void swap(Class &);

没有警告,优点是赋值速度更快,因为按值复制实际上比在方法体中创建临时对象更有效。

现在我们有了这个临时对象,我们只需对临时对象执行交换即可。当它超出范围时它会自动销毁,我们现在在我们的对象中拥有来自运算符右侧的值。

参考资料

https://www.linkedin.com/learning/c-plus-plus-advanced-topics/rule-of-five?u=67551194 https://en.cppreference.com/w/cpp/language/rule_of_three

于 2021-06-21T06:34:23.103 回答