在基类和派生类中复制和交换

copy & swap in base and derived class

本文关键字:复制 交换 派生 基类      更新时间:2023-10-16

我最近读了关于copy&swap的文章,现在正在尝试在基类和派生类中实现ctors。我的基类和派生类中都有四个构造函数,但是我不确定如何实现派生类的赋值运算符。

explicit Base(int i) : m_i{i} {}
Base(const Base & other) : m_i{other.m_i}
Base(Base && other) : Base(0) { swap(*this, other); }
Base & operator=(Base other) { swap(*this, other); return *this; }
friend void swap(Base & a, Base & b) noexcept {
    using std::swap;
    swap(a.m_i, b.m_i);
}
explicit Derived(int j) : Base(42), m_j(j) {}
Derived(const Derived & other) : Derived(other.m_j) {}
Derived(Derived && other) : Derived(other.m_j) { swap(*this, other); }
Derived & operator=(Derived other) { /*???*/ }
friend void swap(Derived & a, Derived & b) noexcept {
    using std::swap;
    swap(a.m_j, b.m_j);
}

考虑尽可能多地使用= default。 如果我们谈论的是公共继承,你真的需要一个虚拟析构函数。

以下是使用复制/交换样式的Base的外观:

class Base
{
    int m_i;
public:
    virtual ~Base() = default;
    Base(const Base& other) = default;
    Base& operator=(Base other) noexcept
    {
        swap(*this, other);
        return *this;
    }
    Base(Base&& other) noexcept
        : Base(0)
    {
        swap(*this, other);
    }
    explicit Base(int i) noexcept
        : m_i{i}
        {}
    friend void swap(Base& a, Base& b) noexcept
    {
        using std::swap;
        swap(a.m_i, b.m_i);
    }
};

与您拥有的唯一区别是我添加了虚拟析构函数,并将= default用于复制构造函数。

现在Derived

class Derived
    : public Base
{
    int m_j;
public:
    Derived(const Derived& other) = default;
    Derived& operator=(Derived other) noexcept
    {
        swap(*this, other);
        return *this;
    }
    Derived(Derived&& other) noexcept
        : Derived(0)
    {
        swap(*this, other);
    }
    explicit Derived(int j) noexcept
        : Base(42)
        , m_j{j}
        {}
    friend void swap(Derived& a, Derived& b) noexcept
    {
        using std::swap;
        swap(static_cast<Base&>(a), static_cast<Base&>(b));
        swap(a.m_j, b.m_j);
    }
};
我让编译器隐式地处理析构

函数,因为编译器会隐式地给我一个在这种情况下做正确事情的虚拟析构函数。

我再次显式默认了复制构造函数。 这纠正了您的版本中忽略复制Base的错误。

operator=看起来就像Base版本一样。

Derived移动构造函数不需要从other移动或复制任何内容,因为它将与other一起swap

Derived swap函数必须交换Base部分以及Derived部分。


现在考虑不使用复制/交换习惯用法。 这可能出奇地容易,在某些情况下,性能更高。

对于Base,您可以将= default用于所有 5 个特殊成员:

class Base
{
    int m_i;
public:
    virtual ~Base() = default;
    Base(const Base&) = default;
    Base& operator=(const Base&) = default;
    Base(Base&&) = default;
    Base& operator=(Base&&) = default;
    explicit Base(int i) noexcept
        : m_i{i}
        {}
    friend void swap(Base& a, Base& b) noexcept
    {
        using std::swap;
        swap(a.m_i, b.m_i);
    }
};

这里真正需要的唯一工作是自定义构造函数和swap函数。

Derived更简单:

class Derived
    : public Base
{
    int m_j;
public:
    explicit Derived(int j) noexcept
        : Base(42)
        , m_j{j}
        {}
    friend void swap(Derived& a, Derived& b) noexcept
    {
        using std::swap;
        swap(static_cast<Base&>(a), static_cast<Base&>(b));
        swap(a.m_j, b.m_j);
    }
};

所有 5 个特殊成员都可以隐式默认!

我们无法在Base中默认它们,因为我们需要指定虚拟析构函数,这会禁止生成移动成员,并且使用用户声明的析构函数不推荐使用复制成员的生成。 但是由于我们不需要在 Derived 中声明析构函数,我们可以让编译器处理所有事情。

由于复制/交换的一大卖点是减少编码,因此具有讽刺意味的是,使用它实际上比让编译器默认特殊成员需要更多的编码。

当然,如果默认值没有做正确的事情,那么不要使用它们。 我只是说默认值应该是你的首选,在复制/交换之前。

你实现op=的方式与

Derived Base完全相同:

Derived& operator=(Derived other) { swap(*this, other); return *this; }

不过,我希望您知道按值传递参数的利弊:

  • 优点:所有值类别只需要一个函数。
  • 不利的一面:xvalues 的第二次移动,除了 prvalues 所需的副本之外,还移动。

需要考虑的其他要点:

  • 经验法则:应该explicit单参数非复制/移动 ctors:您真的不希望从intBase进行隐式转换......
  • 您忘记为Derived重新实现swap(交换所有子对象,包括基对象和成员(。但是,如果Derived不添加任何成员,您可能会放弃它。