Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/149.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 复制和交换的习惯用法是什么?_C++_Copy Constructor_Assignment Operator_C++ Faq_Copy And Swap - Fatal编程技术网

C++ 复制和交换的习惯用法是什么?

C++ 复制和交换的习惯用法是什么?,c++,copy-constructor,assignment-operator,c++-faq,copy-and-swap,C++,Copy Constructor,Assignment Operator,C++ Faq,Copy And Swap,这个成语是什么?什么时候用?它解决了哪些问题?当使用C++11时,习惯用法会改变吗 虽然在很多地方都提到过它,但我们没有任何单一的“它是什么”问题和答案,所以就在这里。以下是之前提到的部分地点列表: 概述 为什么我们需要复制和交换习惯用法? 任何管理资源的类(包装器,如智能指针)都需要实现。虽然复制构造函数和析构函数的目标和实现很简单,但复制赋值操作符可以说是最微妙和最困难的。应该怎么做?需要避免哪些陷阱 复制和交换习惯用法就是解决方案,它优雅地帮助赋值操作符实现两件事:避免和提供一个

这个成语是什么?什么时候用?它解决了哪些问题?当使用C++11时,习惯用法会改变吗

虽然在很多地方都提到过它,但我们没有任何单一的“它是什么”问题和答案,所以就在这里。以下是之前提到的部分地点列表:

概述 为什么我们需要复制和交换习惯用法? 任何管理资源的类(包装器,如智能指针)都需要实现。虽然复制构造函数和析构函数的目标和实现很简单,但复制赋值操作符可以说是最微妙和最困难的。应该怎么做?需要避免哪些陷阱

复制和交换习惯用法就是解决方案,它优雅地帮助赋值操作符实现两件事:避免和提供一个新的解决方案

它是如何工作的? ,它使用复制构造函数的功能创建数据的本地副本,然后使用
swap
函数获取复制的数据,用新数据交换旧数据。然后,临时副本将销毁,并带走旧数据。我们只剩下一份新数据的副本

为了使用复制和交换习惯用法,我们需要三样东西:工作的复制构造函数、工作的析构函数(两者都是任何包装器的基础,因此无论如何都应该是完整的)和
交换
函数

交换函数是一个非抛出函数,它将一个类的两个对象(成员对成员)交换。我们可能会尝试使用
std::swap
而不是提供我们自己的,但这是不可能的<代码>标准::交换
在其实现中使用复制构造函数和复制赋值运算符,我们最终将尝试根据赋值运算符本身定义赋值运算符

(不仅如此,对
swap
的非限定调用将使用我们的自定义swap操作符,跳过
std::swap
将需要的类的不必要的构造和销毁。)


深入的解释 目标 让我们考虑一个具体的例子。我们想要在一个无用的类中管理一个动态数组。我们从工作构造函数、复制构造函数和析构函数开始:

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array
{
public:
    // (default) constructor
    dumb_array(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new int[mSize]() : nullptr)
    {
    }

    // copy-constructor
    dumb_array(const dumb_array& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr)
    {
        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }

    // destructor
    ~dumb_array()
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};
我们说我们完了;现在,它可以管理阵列,而不会出现泄漏。但是,它有三个问题,在代码中顺序标记为
(n)

  • 第一个是自我分配测试。
    此检查有两个目的:它是一种简单的方法,可以防止我们在自我分配时运行不必要的代码,并且可以保护我们免受细微的错误(例如删除数组只是为了尝试复制它)。但在所有其他情况下,它只会减慢程序的速度,并在代码中起到噪音的作用;自我分配很少发生,因此大多数情况下,此检查是一种浪费。
    如果操作员没有它也能正常工作,那就更好了

  • 第二,它只提供了基本的例外保证。如果
    new int[mSize]
    失败,
    *此
    将被修改。(即大小错误,数据丢失!)
    对于强有力的例外保证,它需要类似于:

     dumb_array& operator=(const dumb_array& other)
     {
         if (this != &other) // (1)
         {
             // get the new data ready before we replace the old
             std::size_t newSize = other.mSize;
             int* newArray = newSize ? new int[newSize]() : nullptr; // (3)
             std::copy(other.mArray, other.mArray + newSize, newArray); // (3)
    
             // replace the old data (all are non-throwing)
             delete [] mArray;
             mSize = newSize;
             mArray = newArray;
         }
    
         return *this;
     }
    
  • 代码已经扩展!这就引出了第三个问题:代码重复

  • 我们的赋值运算符有效地复制了我们在别处已经编写的所有代码,这是一件可怕的事情

    在我们的例子中,它的核心只有两行(分配和拷贝),但是对于更复杂的资源,这段代码膨胀可能是相当麻烦的。我们应该努力做到永不重复

    (有人可能会想:如果正确管理一个资源需要这么多代码,那么如果我的类管理多个资源呢?
    虽然这似乎是一个值得关注的问题,而且确实需要非常重要的
    try
    /
    catch
    子句,但这不是一个问题。
    这是因为类应该管理!)

    成功的解决方案 如上所述,复制和交换习惯用法将解决所有这些问题。但是现在,除了一个要求外,我们有所有的要求:一个
    swap
    函数。虽然三的规则成功地要求存在复制构造函数、赋值运算符和析构函数,但它实际上应该被称为“三大半”:在任何时候,您的类管理资源时,提供
    交换
    函数也是有意义的

    我们需要向类中添加交换功能,我们的做法如下†:

    class dumb_array
    {
    public:
        // ...
    
        friend void swap(dumb_array& first, dumb_array& second) // nothrow
        {
            // enable ADL (not necessary in our case, but good practice)
            using std::swap;
    
            // by swapping the members of two objects,
            // the two objects are effectively swapped
            swap(first.mSize, second.mSize);
            swap(first.mArray, second.mArray);
        }
    
        // ...
    };
    
    (这就是为什么
    公共朋友交换
    )现在我们不仅可以交换我们的
    dumb_数组
    ,而且交换通常可以更有效;它只是交换指针和大小,而不是分配和复制整个数组。除了在功能性和效率方面的优势之外,我们现在已经准备好实现复制和交换习惯用法

    无需进一步说明,我们的赋值运算符为:

    dumb_array& operator=(dumb_array other) // (1)
    {
        swap(*this, other); // (2)
    
        return *this;
    }
    
    就这样!一举,所有三个问题都能优雅地同时解决

    它为什么有效? 我们首先注意到一个重要的选择:参数参数按值取值。虽然我们可以很容易地做到以下几点(事实上,许多简单的习语实现都可以做到):

    我们输了一场比赛。不仅如此,这个选择在C++11中也是至关重要的,后面将讨论这个问题。(一般来说,下面是一条非常有用的指导原则:如果要在函数中复制某些内容,请让编译器在参数列表中执行此操作。”——)

    无论哪种方式,这种获取资源的方法都是消除代码重复的关键:我们可以使用复制构造函数中的代码进行复制,而不需要重复任何一点。既然复制完成了,我们就可以交换了

    请注意,进入该功能后,所有新数据都已分配、复制并准备好使用。这给了我们一个强有力的免费例外保证:我们不会
    dumb_array& operator=(dumb_array other) // (1)
    {
        swap(*this, other); // (2)
    
        return *this;
    }
    
    dumb_array& operator=(const dumb_array& other)
    {
        dumb_array temp(other);
        swap(*this, temp);
    
        return *this;
    }
    
    class dumb_array
    {
    public:
        // ...
    
        // move constructor
        dumb_array(dumb_array&& other) noexcept ††
            : dumb_array() // initialize via default constructor, C++11 only
        {
            swap(*this, other);
        }
    
        // ...
    };
    
    dumb_array& operator=(dumb_array other); // (1)
    
    T& operator=(T tmp)
    {
        this->swap(tmp);
        return *this;
    }
    
    friend void swap(A& first, A& second) {
    
        std::swap(first.size, second.size);
        std::swap(first.arr, second.arr);
    
    }
    
    void swap(A& other) {
    
        std::swap(size, other.size);
        std::swap(arr, other.arr);
    
    }
    
    X& operator=(X rhs)
    {
        swap(rhs);
        return *this;
    }
    
    struct X
    {
        T* p_;
        size_t size_;
        X& operator=(const X& rhs)
        {
            delete[] p_;  // OUCH!
            p_ = new T[size_ = rhs.size_];
            std::copy(p_, rhs.p_, rhs.p_ + rhs.size_);
        }
        ...
    };
    
    struct Client
    {
        IP_Address ip_address_;
        int socket_;
        X(const X& rhs)
          : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_))
        { }
    };
    
    void fs(std::vector<T, A> & a, std::vector<T, A> & b)
    { 
        a.swap(b);
        b.clear(); // not important what you do with b
    }
    
    void fm(std::vector<T, A> & a, std::vector<T, A> & b)
    {
        a = std::move(b);
    }