在基类和派生类中复制和交换
copy & swap in base and derived class
我最近读了关于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:您真的不希望从int
到Base
进行隐式转换...... - 您忘记为
Derived
重新实现swap
(交换所有子对象,包括基对象和成员(。但是,如果Derived
不添加任何成员,您可能会放弃它。
- 复制和交换习惯用法与移动操作之间的交互
- 复制和交换习语和迭代器
- 为什么当我做复制和交换习语时不调用我的复制构造函数?
- 为什么基类中的复制和交换会导致派生类中的复制赋值运算符被隐式删除?
- 共享指针的复制和交换效率
- 复制交换习惯用法-我们可以在这里使用动态强制转换操作吗
- 错误:使用复制和交换习惯用法的交换函数中"operator="的重载不明确
- C++移动分配可防止复制交换习惯用法
- 复制交换偶像:如果我更改类成员怎么办?
- 实现交换/复制功能:有没有更好的方法
- 如何在没有复制赋值运算符的情况下交换两个对象
- C++ 通过移动而不是复制来交换数组元素
- 在 C++11 中实现复制和交换习语的更好方法
- 在低级别实现交换复制位时可能会出现什么问题
- 当涉及分配器时,是否有类似于复制和交换习惯用法的东西
- 如何最好地处理具有未初始化内存的复制交换习惯用法
- 移动构造函数和赋值操作符,使用复制-交换习惯实现
- 重用复制-交换习惯用法
- 复制-交换总是最好的解决方案吗?
- 复制-交换习语的低效率