副本&交换基类和派生类

时间:2014-09-22 11:32:20

标签: c++11 constructor derived-class base-class

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

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

2 个答案:

答案 0 :(得分:6)

尽可能考虑使用= 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中的任何内容,因为它swapother

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


现在考虑使用复制/交换习惯用法。这可以令人惊讶地更容易,并且在某些情况下,表现更高。

对于Base,您可以对所有5位特殊成员使用= default

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中声明析构函数,所以我们可以让编译器处理所有内容。

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

当然,如果默认设置不正确,那么不要使用它们。我只是说默认值应该是你的第一选择,在复制/交换之前。

答案 1 :(得分:3)

op=Derived完全相同[{1}}实施Base

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

我希望你知道在那里通过值传递参数的上下方面,但是:

  • Up-side:所有值类别只需要一个功能。
  • Down-Side:xvalues的第二步,除了prvalues所需的副本之外还要移动。

需要考虑的其他要点:

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