【发布时间】:2020-04-06 22:45:21
【问题描述】:
以下三个目标会导致冲突:
- 我了解到,
std::move什么都不做,但之后std::move(someDObject)对象someDObject可能被canibalized,你不应该访问someDObject - CleanCode-SingleResponsibiliPattern:一个类应该负责 仅用于一项功能
- CodingStyle-DataEncapsulation:一个类不应该公开实现细节,甚至不应该暴露给派生类
想象一个(不可复制构造的)类具有两个不同的特征。 SRB 的原因之一是在Base 中实现,第二个在Derived : public Base 中实现。这两个类都将它们的数据保存在一个可移动的容器中,例如std::list<std::string>,分别称为m_dataDerived 和m_dataBase。数据封装原因m_dataBase应该是private:。
这导致了如何为派生类实现move-Constructor的问题。要么:
Derived::Derived(Derived &&rhs)
: Base(std::move(rhs))
, m_dataDerived(std::move(rhs.m_dataDerived))
{}
这在语法上违反了规则一,在std::move(rhs) 之后不要访问rhs 但是m_dataDerived 不能被Base 的构造函数canibalized,因为Base 对此一无所知=> 因此m_dataDerived 应该仍然有效。我不喜欢应该。
反过来又会导致其他问题:
Derived::Derived(Derived &&rhs)
{
m_dataBase = std::move(rhs.m_dataBase);
m_dataDerived = std::move(rhs.m_dataDerived);
}
为此,您需要将 m_dataBase 视为 protected: 破坏 DataEncapsulation 的原因。此外,Base 的每个更改都必须在所有派生的 move-Contructors 中完成,这会导致维护问题。
缺少的是std::move(OnlyBasepart ofrhs)。有没有办法做到这一点?
首选的编译示例在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;
}
【问题讨论】:
-
小修正:您可以拨打
std::move任何您想要的。只有当生成的右值引用以某种方式被消耗时,移动的值才会变得无效。 -
这能回答你的问题吗? Move constructor for derived class
-
是的,隐藏的知识使变体 1 起作用。然而,这对我来说看起来很残酷。将
rhs移出并在下一行访问rhs.m_dataDerived。 -
感谢 JBL,但这篇文章不涉及数据封装和私有成员。正如我在“缺少的是......”中指出的那样,我想得到建议,最终哪种变体更好,甚至更好的第三种方式。
-
@Xantopp 确实没有,但它向您表明,为了保持封装和思想,您想要的第一个变体是“打破”不使用移动后已移动的东西的规则, 实际上在这种情况下很好。
标签: c++ move move-semantics