如何为具有私有成员的派生类实现移动构造函数



以下三个目标会导致冲突:

  • 我了解到,std::move什么都不做,但是在之后std::move(someDObject)对象someDObject可能被卡尼巴尔化,您不应访问someDObject
  • CleanCode-SingleResponsibiliPattern:一个类应该负责 仅适用于一个功能
  • CodingStyle-DataEncapsulation:类不应该公开实现细节,甚至不应该公开派生类

假设一个(不可复制构造(类具有两个不同的功能。SRB的原因之一在Base年实现,第二个在Derived : public Base年实现。这两个类都将它们的数据保存在一个可移动的Conainer中,就像std::list<std::string>一样,称为m_dataDerived并相互m_dataBase。数据封装的原因m_dataBaseprivate:

这导致了如何为派生类嵌入 move-Constructor的问题。也:

Derived::Derived(Derived &&rhs)
: Base(std::move(rhs))
, m_dataDerived(std::move(rhs.m_dataDerived))
{}

这在语法上违反了规则一,std::move(rhs)之后不访问rhs然而m_dataDerived不能被Base的构造函数可以化,因为Base不知道它=>因此m_dataDerived应该仍然有效。我不喜欢应该

反之则会导致其他问题:

Derived::Derived(Derived &&rhs)
{
m_dataBase = std::move(rhs.m_dataBase);
m_dataDerived = std::move(rhs.m_dataDerived);
}

为此,您需要将m_dataBase视为破坏DataEncapsulationprotected:的原因。 此外,必须在所有派生的移动构造器中完成Base的每一次更改,这会导致维护问题。

缺少的是一些std::move(只有Baserhs)的一部分.有没有办法这样做?

第一个选择的编译示例是在onlineGdb上(但是使用std::vector而不是std::list(。

此外,下面列出的代码:

#include <iostream>
#include <string>
#include <vector>
#include  <algorithm>

class Base;
class Base {
public:
Base() {};
Base(Base &&rhs)  {
std::cout << "BaseMove_Construktor(" << rhs.m_list.size() << ") --> " ;
m_list = std::move(rhs.m_list);
std::cout << m_list.size() <<  std::endl;
}
Base(std::initializer_list<std::string> &&p_list) {
int i=0;
m_list.resize(p_list.size());
for(auto it = std::begin(p_list); it != std::end(p_list); ++it) {
m_list[i++] = *it;
}
};
friend std::ostream &::operator<<(std::ostream & oStream, Base const &rhs);
friend std::ostream &::operator<<(std::ostream & oStream, Base &&rhs);
int size() { return m_list.size(); }
private:
std::vector<std::string> m_list;
};
class Derived : public Base {
public:
Derived() {};
Derived(Derived &&rhs) : Base(std::move(rhs)) {
std::cout << "DerivedMove_Construktor(" << Base::size() << ',' << rhs.m_numbers.size() << ')' << std::endl;
m_numbers = std::move(rhs.m_numbers);
}
Derived(std::initializer_list<std::string> &&p_list, std::initializer_list<double> &&p_numbers) 
: Base(std::move(p_list))
{
int i=0;
std::cout << "Derived-List_Construktor(" << Base::size() << ',' << p_numbers.size() << ')' << std::endl;
m_numbers.resize(p_numbers.size());
for(auto it = std::begin(p_numbers); it != std::end(p_numbers); ++it) {
m_numbers[i++] = *it;
}
};
friend std::ostream &::operator<<(std::ostream & oStream, Derived const &rhs);
private:
std::vector<double> m_numbers;
};

std::ostream &operator<<(std::ostream & oStream, Base const &rhs) 
{
oStream << "{ "; 
for(auto it = std::begin(rhs.m_list); it != std::end(rhs.m_list); ++it) 
{
oStream << '"' << *it << "", ";
}
oStream << '}'; 
}
std::ostream &operator<<(std::ostream & oStream, Base &&rhs) 
{
oStream << "{m: "; 
for(auto it = std::begin(rhs.m_list); it != std::end(rhs.m_list); ++it) 
{
oStream << '"' << *it << "", ";
}
oStream << '}'; 
}
std::ostream &operator<<(std::ostream & oStream, Derived const &rhs) 
{
oStream << '{'; 
for(auto it = std::begin(rhs.m_numbers); it != std::end(rhs.m_numbers); ++it) 
{
oStream <<  *it << ", ";
}
oStream << (Base const &)rhs << '}'; 
}
int main()
{
std::cout << "Hello World" << std::endl;
std::cout << Base({ "tafel", "kreide", "Schwamm" }) << std::endl;
Base base{ "tafel", "kreide", "Schwamm" };
std::cout << base << std::endl;
Derived derived({ "tafel", "kreide", "Schwamm" }, { 0.2342, 8.639 });
std::cout << derived << std::endl;
Derived derivedCopy(std::move(derived));
std::cout << "derived is empty now:   " << derived << std::endl;
std::cout << "derivedCopy holds data: " << derivedCopy << std::endl;
return 0;
}
Derived::Derived(Derived &&rhs)
: Base(std::move(rhs))
, m_dataDerived(std::move(rhs.m_dataDerived))
{}

此代码是正确的和惯用的。如果需要,可以采用阐明如何仅移动Base部分的方式编写它:

Derived::Derived(Derived &&rhs)
: Base(std::move(static_cast<Base&&>(rhs)))
, m_dataDerived(std::move(rhs.m_dataDerived))
{}

我们只移动DerivedBase子对象。事实上,rhsBase子对象在使用它调用Basemove 构造函数后处于有效但(按照惯例(未定义的状态,因此我们最好不要对此做出任何假设。但我们显然没有碰过m_dataDerived,所以事后离开它很好。

不过,我建议不要编写上述代码(带有额外的static_cast(。对于初学者来说,std::move实际上变得毫无意义(但省略它会使代码的可读性更加降低(。在移动构造函数的上下文中,直接从std::move(rhs)移动构造基础的意图和效果应该是完全清晰和惯用的。

你的第一条规则("我了解到,std::move什么都不做,但std::move(someDObject)后对象someDObject可能会被卡尼巴尔化,你不应该访问someDObject">(也是不准确的:

  1. 对象可以被蚕食,但只能通过可以蚕食的操作。因此,在调用std::move后访问对象并不一定是坏事(但大概有人把std::move放在那里是有原因的,假设蚕食不会出错(。

  2. 您可以访问移自对象。但是你不应该对它做任何对其状态做出任何假设的事情(除了它是否有效,这是最终销毁所必需的(。在标准库术语中,您只能对没有前提条件的对象使用操作。因此,您可以reset从中移动的std::unique_ptr,可以在移自std::vector上调用size()等等。

这当然与移动施工不是很相关,但了解到底发生了什么是值得的。

最新更新