【问题标题】:Interaction between copy-and-swap idiom and move operations复制和交换习语和移动操作之间的交互
【发布时间】:2020-05-27 07:31:51
【问题描述】:

我正在尝试制作一个程序来实现“复制和交换”习语和move control operations之间的交互,所以我写了这段代码:

class PInt
{
public:
    PInt(int = 0);
    PInt(const PInt&);
    PInt(PInt&&) noexcept;
    PInt& operator=(PInt);
    ~PInt();

    int* getPtr()const;

private:
    int* ptr;
    friend void swap(PInt&, PInt&);
};

PInt::PInt(int x) : 
    ptr(new int(x))
{
    std::cout << "ctor\n";
}

PInt::PInt(const PInt& rhs) :
    ptr(new int(rhs.ptr ? *rhs.ptr : 0))
{
    std::cout << "copy-ctor\n";
}

PInt::PInt(PInt&& rhs) noexcept :
    ptr(rhs.ptr)
{
    std::cout << "move-ctor\n";
    rhs.ptr = nullptr; // putting rhs in a valid state
}


PInt& PInt::operator=(PInt rhs)
{
    std::cout << "copy-assignment operator\n";
    swap(*this, rhs);
    return *this;
}

PInt::~PInt()
{
    std::cout << "dtor\n";
    delete ptr;
}

void swap(PInt& lhs, PInt& rhs)
{
    std::cout << "swap(PInt&, PInt&\n";
    using std::swap;
    swap(lhs.ptr, rhs.ptr);
}

PInt gen_PInt(int x)
{
    return {x};
}

int main()
{
    PInt pi1(1), pi2(2);
    //pi1 = pi2; // 1
    //pi1 = PInt{}; // 2
    //pi1 = std::move(pi2); // 3
    pi1 = std::move(PInt{}); // 4


}
  • 对我来说一切都很好,所以我认为在1 中,copy-ctor 由 copy-asignment 运算符调用以初始化其参数(按值获取),然后使用交换。在“2”中,我是从 r 值分配的,因此我认为编译器应用了一些“复制省略”优化;在复制赋值运算符中直接创建一个对象。

  • 我不确定的是 3 和 4。所以这里是 3 和 4 的结果:

    取消注释第 3 行:

    ctor
    ctor
    move - ctor
    copy - assignment operator
    swap(PInt&, PInt &
    dtor
    dtor
    dtor
    

取消注释第 4 行:

    ctor
    ctor
    ctor
    move - ctor
    copy - assignment operator
    swap(PInt&, PInt &
    dtor
    dtor
    dtor
    dtor
  • 为什么 3 和 4 使用 std::move 却调用了 n 个额外的构造函数?

** 哪个是有效的:定义一个复制/移动赋值运算符,取值或两个单独的版本:复制赋值和移动赋值?因为每次调用它的一个版本要么调用(额外调用)copy-ctor 或 move-ctor 来初始化它的参数吗?

【问题讨论】:

  • 代码中没有"Done!"。请提供与输出匹配的代码
  • 请注意,您的operator= 输出copy-assignment operator,但它实际上同时支持复制赋值和移动赋值,因为PInt 同时具有复制构造函数和移动构造函数。使用复制与移动的选择在调用站点处理,具体取决于分配的是左值还是右值。如果要区分,请定义单独的运算符:PInt&amp; operator=(const PInt&amp;) 用于复制,PInt&amp; operator=(PInt&amp;&amp;) 用于移动
  • @RemyLebeau:我的版本在定义单独的复制分配和移动分配方面是否有效?
  • @Maestro 他们差不多。我仅在您确实需要区分时才提及这一点,例如出于记录目的,以便您可以记录复制与移动操作

标签: c++ c++11 move-constructor copy-and-swap


【解决方案1】:

为什么 3 和 4 使用 std::move 却调用了 n 个额外的构造函数?

3 和 4 的“额外”(移动)构造函数是在此处创建作为参数的对象:

PInt& PInt::operator=(PInt rhs)
                      ^^^^^^^^

4 的“额外”构造函数是创建这个临时的:

pi1 = std::move(PInt{}); // 4
                ^^^^^^

【讨论】:

  • 我可以看到,如果我分别定义它们,那么移动赋值不会调用移动构造函数,因为它需要一个右值引用,而一对二按值传递。问题:哪个更有效:复制/移动赋值运算符还是定义单独的?
  • @Maestro 移动语义旨在提高效率。您的 PInt 类非常轻量级,因此您是实现单个复制+移动赋值运算符还是两个单独的复制/移动运算符并不重要。显然,在绝对数量的指令中,实现单独的运算符可能避免了对临时对象的一组构造函数/析构函数调用,但开销如此之小以至于不是真正的问题。更重要的是能够编写可以长期维护的代码。
猜你喜欢
  • 2011-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 2021-09-23
  • 2011-11-22
  • 1970-01-01
  • 2011-10-13
相关资源
最近更新 更多