在C ++ 11中实现复制和交换习惯的更好方法 [英] A better way to implement copy-and-swap idiom in C++11

查看:63
本文介绍了在C ++ 11中实现复制和交换习惯的更好方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到很多代码在复制和交换方面实现了五个规则,但是我认为我们可以使用移动函数来替换交换函数,如以下代码所示:

  #include< algorithm> 
#include< cstddef>

类DumbArray {
public:
DumbArray(std :: size_t size = 0)
:size_(size),array_(size_?new int [size_] ():nullptr){
}

DumbArray(const DumbArray&that)
:size_(that.size _),array_(size_?new int [size _]:nullptr) {
std :: copy(that.array_,that.array_ + size_,array_);
}

DumbArray(DumbArray& that):DumbArray(){
move_to_this(that);
}

〜DumbArray(){
delete [] array_;
}

DumbArray& operator =(DumbArray that){
move_to_this(that);
return * this;
}

私人:
void move_to_this(DumbArray&that){
delete [] array_;
array_ = that.array_;
size_ = that.size_;
that.array_ = nullptr;
that.size_ = 0;
}

私人:
std :: size_t size_;
int * array_;
};

我认为这段代码


  1. 异常安全

  2. 需要较少的键入操作,因为许多函数只调用move_to_this(),并且将复制分配和移动分配统一在一个函数中

  3. 比复制和交换更有效,因为交换涉及3个分配,而这里只有2个分配,此代码不会遇到此链接

对吗?



谢谢



编辑:


  1. 正如@Leon所指出的那样,也许需要一个专用的资源释放功能,以避免 move_to_this()和析构函数
  2. $ b中的代码重复$ b
  3. 正如@thorsan指出的那样,出于对性能的极端关注,最好将 DumbArray& operator = {DumbArray that} {move_to_this(that);返回* this; } 放入 DumbArray& operator =(const DumbArray& that){DumbArray temp(that); move_to_this(temp);返回* this; } (感谢@MikeMB)和 DumbArray& operator =(DumbArray& that){move_to_this(that);返回* this; } 以避免额外的移动操作



    添加了一些调试打印后,我发现<$并没有多余的移动c $ c> DumbArray&运算符=(DumbArray that){} 当您将其称为移动分配


  4. @ErikAlapää指出,一个自我-需要在 move_to_this()


  5. <中删除 之前进行赋值检查/ ol>

    解决方案

    内联,但简要地:




    • ,如果可能的话,您希望所有移动分配和构造函数都设为 noexcept 。如果启用此功能,则标准库会更快很多,因为它可以消除对对象序列进行重新排序的算法中的任何异常处理。


    • 如果要定义自定义析构函数,请使其无例外。为什么要打开潘多拉盒子?我错了。


    • 在这种情况下,提供强大的异常保证很容易,而且几乎不花任何钱,所以让我们这样做。




    代码:

      #include< ; algorithm> 
    #include< cstddef>

    类DumbArray {
    public:
    DumbArray(std :: size_t size = 0)
    :size_(size),array_(size_?new int [size_] ():nullptr){
    }

    DumbArray(const DumbArray&that)
    :size_(that.size _),array_(size_?new int [size _]:nullptr) {
    std :: copy(that.array_,that.array_ + size_,array_);
    }

    //移动构造函数成为所有移动操作的核心。
    //注意,它不是noexcept-这意味着我们的对象在被std ::容器包含时会表现良好
    //:
    DumbArray(DumbArray& amp; that)noexcept
    :size_(that.size_)
    ,array_(that.array_)
    {
    that.size_ = 0;
    that.array_ = nullptr;
    }

    //不例外,否则可能发生各种令人讨厌的事情
    〜DumbArray()//不例外-这是隐含的。
    {
    delete [] array_;
    }

    //我看到您正在通过重复使用赋值运算符
    //来进行复制分配和移动分配,但不幸的是
    //这使我们无法使移动分配运算符
    // noexcept(请参阅稍后)
    DumbArray& operator =(const DumbArray& that)
    {
    //复制交换习惯为免费提供了强有力的异常保证。
    DumbArray(that).swap(* this);
    return * this;
    }

    //移动分配现在为noexcept(因为move-constructor为noexcept
    //和swap为noexcept)这使得DumbArray
    的矢量操作/ /
    //(例如插入,分区,排序等),比原先的速度快了多个数量级
    DumbArray& operator = {DumbArray&& that)noexcept {
    DumbArray(std :: move(that))。swap(* this);
    return * this;
    }


    //提供noexcept交换。这是所有移动和复制操作
    //的核心,并再次提供,它可以帮助标准容器和算法
    //提高效率。存在标准习语是因为它们有效。
    void swap(DumbArray&that)noexcept {
    std :: swap(size_,that.size_);
    std :: swap(array_,that.array_);
    }

    私人:
    std :: size_t size_;
    int * array_;
    };

    在移动分配运算符中可以进行进一步的性能改进。



    我提供的解决方案可以保证从中移出的数组将为空(资源已释放)。这可能不是您想要的。例如,如果您分别跟踪DumbArray的容量和大小(例如,如std :: vector),那么您很可能希望 this 中的所有已分配内存为在移动后保留在那个中。然后,这将允许将 分配给,而可能在没有其他内存分配的情况下消失。 ,我们只需按照(noexcept)swap来实现移动分配运算符:



    因此从此开始:

      /// @pre必须处于有效状态
    /// @post保证为空()且未分配()
    // /
    DumbArray& operator = {DumbArray&& that)noexcept {
    DumbArray(std :: move(that))。swap(* this);
    return * this;
    }

    为此:

      /// @pre必须处于有效状态
    /// @post将处于未定义但有效的状态
    DumbArray& operator =(DumbArray&& that)noexcept {
    swap(that);
    return * this;
    }

    对于DumbArray,可能值得在其中使用更宽松的形式练习,但要注意细微的错误。



    例如

      DumbArray x = {....}; 
    do_something(std :: move(x));

    //此处:如果实现完全破坏性的
    //变体,则会出现段错误。经过优化的变体*可能*不会崩溃,它可能只对某些先前使用的数据执行
    // something_else。
    //根据您的应用程序,这可能会带来安全风险

    something_else(x);


    I saw many code implementing rule of five in terms of copy and swap, but I think we can use a move function to replace the swap function as in the following code:

    #include <algorithm>
    #include <cstddef>
    
    class DumbArray {
    public:
        DumbArray(std::size_t size = 0)
            : size_(size), array_(size_ ? new int[size_]() : nullptr) {
        }
    
        DumbArray(const DumbArray& that)
            : size_(that.size_), array_(size_ ? new int[size_] : nullptr) {
            std::copy(that.array_, that.array_ + size_, array_);
        }
    
        DumbArray(DumbArray&& that) : DumbArray() {
            move_to_this(that);
        }
    
        ~DumbArray() {
            delete [] array_;
        }
    
        DumbArray& operator=(DumbArray that) {
            move_to_this(that);
            return *this;
        }
    
    private:
        void move_to_this(DumbArray &that) {
            delete [] array_;
            array_ = that.array_;
            size_ = that.size_;
            that.array_ = nullptr;
            that.size_ = 0;
       }
    
    private:
        std::size_t size_;
        int* array_;
    };
    

    This code, I think

    1. Exception safe
    2. Require less typing, as many function just call move_to_this(), and copy assignment and move assignment are unified in one single function
    3. More efficient than copy-and-swap, as swap involves 3 assignments, while here just 2, and this code doesn't suffer the problems mentioned in This Link

    Am I right?

    Thanks

    Edit:

    1. As @Leon pointed out, maybe a dedicated function for freeing resource is needed, to avoid code duplication in move_to_this() and destructor
    2. As @thorsan pointed out, for extreme performance concern, it's better to seperate DumbArray& operator=(DumbArray that) { move_to_this(that); return *this; } into DumbArray& operator=(const DumbArray &that) { DumbArray temp(that); move_to_this(temp); return *this; }(thanks to @MikeMB) and DumbArray& operator=(DumbArray &&that) { move_to_this(that); return *this; } to avoid an extra move operatoin

      After adding some debug print, I found that no extra move is involved in DumbArray& operator=(DumbArray that) {} when you call it as a move assignment

    3. As @Erik Alapää pointed out, a self-assignment check is needed before delete in move_to_this()

    解决方案

    comments inline, but briefly:

    • you want all move assignments and move constructors to be noexcept if at all possible. The standard library is much faster if you enable this, because it can elide any exception handling from algorithms which reorder a sequence of your object.

    • if you're going to define a custom destructor, make it noexcept. Why open pandora's box? I was wrong about this. It's noexcept by default.

    • In this case, providing the strong exception guarantee is painless and costs almost nothing, so let's do that.

    code:

    #include <algorithm>
    #include <cstddef>
    
    class DumbArray {
    public:
        DumbArray(std::size_t size = 0)
        : size_(size), array_(size_ ? new int[size_]() : nullptr) {
        }
    
        DumbArray(const DumbArray& that)
        : size_(that.size_), array_(size_ ? new int[size_] : nullptr) {
            std::copy(that.array_, that.array_ + size_, array_);
        }
    
        // the move constructor becomes the heart of all move operations.
        // note that it is noexcept - this means our object will behave well
        // when contained by a std:: container
        DumbArray(DumbArray&& that) noexcept
        : size_(that.size_)
        , array_(that.array_)
        {
            that.size_ = 0;
            that.array_ = nullptr;
        }
    
        // noexcept, otherwise all kinds of nasty things can happen
        ~DumbArray() // noexcept - this is implied.
        {
            delete [] array_;
        }
    
        // I see that you were doing by re-using the assignment operator
        // for copy-assignment and move-assignment but unfortunately
        // that was preventing us from making the move-assignment operator
        // noexcept (see later)
        DumbArray& operator=(const DumbArray& that)
        {
            // copy-swap idiom provides strong exception guarantee for no cost
            DumbArray(that).swap(*this);
            return *this;
        }
    
        // move-assignment is now noexcept (because move-constructor is noexcept
        // and swap is noexcept) This makes vector manipulations of DumbArray
        // many orders of magnitude faster than they would otherwise be
        // (e.g. insert, partition, sort, etc)
        DumbArray& operator=(DumbArray&& that) noexcept {
            DumbArray(std::move(that)).swap(*this);
            return *this;
        }
    
    
        // provide a noexcept swap. It's the heart of all move and copy ops
        // and again, providing it helps std containers and algorithms 
        // to be efficient. Standard idioms exist because they work.
        void swap(DumbArray& that) noexcept {
            std::swap(size_, that.size_);
            std::swap(array_, that.array_);
        }
    
    private:
        std::size_t size_;
        int* array_;
    };
    

    There is one further performance improvement one could make in the move-assignment operator.

    The solution I have offered provides the guarantee that a moved-from array will be empty (with resources deallocated). This may not be what you want. For example if you tracked the capacity and the size of a DumbArray separately (for example, like std::vector), then you may well want any allocated memory in this to be retained in that after the move. This would then allow that to be assigned to while possibly getting away without another memory allocation.

    To enable this optimisation, we simply implement the move-assign operator in terms of (noexcept) swap:

    so from this:

        /// @pre that must be in a valid state
        /// @post that is guaranteed to be empty() and not allocated()
        ///
        DumbArray& operator=(DumbArray&& that) noexcept {
            DumbArray(std::move(that)).swap(*this);
            return *this;
        }
    

    to this:

        /// @pre that must be in a valid state
        /// @post that will be in an undefined but valid state
        DumbArray& operator=(DumbArray&& that) noexcept {
            swap(that);
            return *this;
        }
    

    In the case of the DumbArray, it's probably worth using the more relaxed form in practice, but beware of subtle bugs.

    e.g.

    DumbArray x = { .... };
    do_something(std::move(x));
    
    // here: we will get a segfault if we implement the fully destructive
    // variant. The optimised variant *may* not crash, it may just do
    // something_else with some previously-used data.
    // depending on your application, this may be a security risk 
    
    something_else(x);   
    

    这篇关于在C ++ 11中实现复制和交换习惯的更好方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆