在 C++11 中实现复制和交换习惯用法的更好方法

2024-02-09

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

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

这段代码,我认为

  1. 异常安全
  2. 需要更少的输入,因为许多函数只调用 move_to_this(),并且复制赋值和移动赋值统一在一个函数中
  3. 比复制和交换更有效,因为交换涉及 3 个赋值,而这里只有 2 个,并且此代码不会遇到 中提到的问题这个链接 http://scottmeyers.blogspot.com/2014/06/the-drawbacks-of-implementing-move.html

我对吗?

Thanks

Edit:

  1. 正如@Leon 指出的,也许需要一个专门的函数来释放资源,以避免代码重复move_to_this()和析构函数
  2. 正如 @thorsan 指出的,出于极端的性能考虑,最好分开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; }(感谢@MikeMB)和DumbArray& operator=(DumbArray &&that) { move_to_this(that); return *this; }以避免额外的移动操作

    添加一些调试打印后,我发现没有涉及额外的动作DumbArray& operator=(DumbArray that) {}当你将其称为移动作业时

  3. 正如@Erik Alapää 指出的,在之前需要进行自分配检查delete in move_to_this()


内嵌评论,但简短:

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

  • 如果您要定义自定义析构函数,请将其设置为 noexcept。为什么要打开潘多拉魔盒? 我错了。默认情况下没有例外。

  • 在这种情况下,提供强大的异常保证是轻松的,几乎不需要任何成本,所以让我们这样做。

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

移动分配运算符还可以进一步提高性能。

我提供的解决方案保证移出的数组将为空(资源被释放)。这可能不是您想要的。例如,如果您分别跟踪 DumbArray 的容量和大小(例如,像 std::vector),那么您很可能希望在this被保留在that搬家后。这将允许that被分配,同时可能在没有其他内存分配的情况下离开。

为了实现这种优化,我们只需根据(noexcept)交换来实现移动分配运算符:

所以从此:

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

对于 DumbArray 来说,在实践中可能值得使用更宽松的形式,但要注意细微的错误。

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);   
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 C++11 中实现复制和交换习惯用法的更好方法 的相关文章

随机推荐