我看到许多代码在 copy-and-swap 方面实现了5的规则,但是我认为我们可以使用移动函数来替换交换函数,如以下代码所示:

#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_;
};

我认为这段代码
  • 异常安全
  • 需要更少的输入,因为许多函数仅调用move_to_this(),并且复制分配和移动分配被统一在一个函数
  • 比 copy-and-swap 更有效,因为交换涉及3个分配,而这里只有2个分配,此代码不会遭受This Link
  • 中提到的问题

    我对吗?

    谢谢

    编辑:
  • 正如@Leon所指出的那样,也许需要一个专用的函数来释放资源,以避免move_to_this()和析构函数
  • 中的代码重复。
  • 如@thorsan所指出的,出于对性能的极端关注,最好将DumbArray& operator=(DumbArray that) { move_to_this(that); return *this; }分为DumbArray& operator=(const DumbArray &that) { DumbArray temp(that); move_to_this(temp); return *this; }(感谢@MikeMB)和DumbArray& operator=(DumbArray &&that) { move_to_this(that); return *this; },以避免额外的移动操作

    添加一些调试打印后,我发现当您将它作为移动分配
  • 调用时,DumbArray& operator=(DumbArray that) {}中没有涉及任何额外的移动
  • 正如@ErikAlapää所指出的那样,在delete
  • 中的move_to_this()之前需要进行自我分配检查

    最佳答案

    内联评论,但简短地:

  • ,如果可能的话,您希望所有移动分配和构造函数都变为noexcept。如果启用此功能,则标准库会更快,因为标准库可以消除对对象序列重新排序的算法中的任何异常处理。
  • 如果要定义自定义析构函数,请使其为noexcept。为什么要打开潘多拉盒子?我错了。默认情况下也不异常(exception)。
  • 在这种情况下,提供强大的异常保证很容易,而且几乎不花任何钱,所以让我们这样做。

  • 代码:
    #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_;
    };
    

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

    我提供的解决方案保证了从中移出的数组将为空(资源已释放)。这可能不是您想要的。例如,如果您分别跟踪DumbArray的容量和大小(例如,类似于std::vector),那么您很可能希望在移动之后将this中分配的所有内存保留在that中。然后这将允许that分配给它,而可能无需其他内存分配就可以离开。

    为了实现这种优化,我们只需按照(noexcept)交换来实现move-assign运算符:

    所以从这个:
        /// @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;
        }
    

    对此:
        /// @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;
        }
    

    就DumbArray而言,在实践中使用更宽松的形式可能值得,但要提防细微的错误。

    例如
    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++ - 在C++ 11中实现 copy-and-swap 习惯的更好方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/37915088/

    10-11 00:35