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

How to implement move-Constructors for derived classes with private members

以下三个目标导致冲突:

想象一个(不可复制构造的)class 具有两个不同的特征。 SRB 的原因之一是在 Base 中实现,第二个在 Derived : public Base 中实现。两个 classes 都将它们的数据保存在一个可移动的 Conainer 中,例如 std::list<std::string>,分别称为 m_dataDerivedm_dataBase。 DataEncapsulation m_dataBase 的原因应该是 private:.

这导致了问题如何为派生的 class 实现移动构造函数。或者:

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 应该 仍然有效。我不喜欢 should.

反过来会导致其他问题:

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

为此,您需要将 m_dataBase 视为 protected: 破坏 DataEncapsulation 的因素。此外,Base 的每个更改都必须在所有派生的移动构造函数中完成,这会导致维护问题。

少了一些std::move(只是Baserhs)的一部分。有办法吗?

A Compiling Example of the first choice is on onlineGdb (however with std::vector instead of 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 子对象在用它调用 Base 移动构造函数后处于有效但(按照惯例)未定义状态,因此我们最好不要对它做任何假设。但我们显然没有触及m_dataDerived,所以之后移动它就可以了。

不过,我建议不要像上面那样编写代码(带有额外的 static_cast)。对于初学者来说,std::move 实际上变得毫无意义(但将其排除在外会使代码的可读性更差)。在移动构造函数的上下文中,直接从 std::move(rhs) 移动构造基的意图和效果应该非常清楚和惯用。

您的第一条规则("I've learned, that std::move does nothing, but after std::move(someDObject) the Object someDObject may be canibalized and you should not access someDObject")也不准确:

  1. 对象可以被蚕食,但只能通过可以蚕食的操作。所以在调用 std::move 之后访问一个对象并不是 必然 坏的(但大概有人把 std::move 放在那里是有原因的,假设你不会出错自相残杀)。

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

这当然与移动构造没有太大关系,但了解到底发生了什么是值得的。