如何使用私有成员为派生 类 实现移动构造函数
How to implement move-Constructors for derived classes with private members
以下三个目标导致冲突:
- 我了解到,
std::move
什么都不做,但是之后
std::move(someDObject)
对象 someDObject
可能已被替换,您不应访问 someDObject
- CleanCode-SingleResponsibiliPattern:一个class应该负责
只有一项功能
- CodingStyle-DataEncapsulation:class 不应公开实现细节,即使不公开派生的 class
想象一个(不可复制构造的)class 具有两个不同的特征。 SRB 的原因之一是在 Base
中实现,第二个在 Derived : public Base
中实现。两个 classes 都将它们的数据保存在一个可移动的 Conainer 中,例如 std::list<std::string>
,分别称为 m_dataDerived
和 m_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(
只是Base
rhs)
的一部分。有办法吗?
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))
{}
我们只移动 Derived
的 Base
子对象。事实上,rhs
的 Base
子对象在用它调用 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
")也不准确:
对象可以被蚕食,但只能通过可以蚕食的操作。所以在调用 std::move
之后访问一个对象并不是 必然 坏的(但大概有人把 std::move
放在那里是有原因的,假设你不会出错自相残杀)。
您可以访问移出的对象。但是你不应该对它做任何关于它的状态的假设(除了它是有效的,这是最终销毁所必需的)。在标准库术语中,您只能使用操作 on/of 没有前提条件的对象。所以你可以 reset
一个从 std::unique_ptr
移出的,你可以在一个移出的 std::vector
上调用 size()
等等。
这当然与移动构造没有太大关系,但了解到底发生了什么是值得的。
以下三个目标导致冲突:
- 我了解到,
std::move
什么都不做,但是之后std::move(someDObject)
对象someDObject
可能已被替换,您不应访问someDObject
- CleanCode-SingleResponsibiliPattern:一个class应该负责 只有一项功能
- CodingStyle-DataEncapsulation:class 不应公开实现细节,即使不公开派生的 class
想象一个(不可复制构造的)class 具有两个不同的特征。 SRB 的原因之一是在 Base
中实现,第二个在 Derived : public Base
中实现。两个 classes 都将它们的数据保存在一个可移动的 Conainer 中,例如 std::list<std::string>
,分别称为 m_dataDerived
和 m_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(
只是Base
rhs)
的一部分。有办法吗?
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))
{}
我们只移动 Derived
的 Base
子对象。事实上,rhs
的 Base
子对象在用它调用 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
")也不准确:
对象可以被蚕食,但只能通过可以蚕食的操作。所以在调用
std::move
之后访问一个对象并不是 必然 坏的(但大概有人把std::move
放在那里是有原因的,假设你不会出错自相残杀)。您可以访问移出的对象。但是你不应该对它做任何关于它的状态的假设(除了它是有效的,这是最终销毁所必需的)。在标准库术语中,您只能使用操作 on/of 没有前提条件的对象。所以你可以
reset
一个从std::unique_ptr
移出的,你可以在一个移出的std::vector
上调用size()
等等。
这当然与移动构造没有太大关系,但了解到底发生了什么是值得的。