gpt4 book ai didi

c++ - 如何为具有私有(private)成员的派生类实现 move 构造函数

转载 作者:行者123 更新时间:2023-12-02 00:04:09 25 4
gpt4 key购买 nike

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

  • 我了解到 std::move什么都不做,但之后 std::move(someDObject)对象 someDObject可能会被蚕食,您不应该访问 someDObject
  • CleanCode-SingleResponsibiliPattern:一个类应该负责仅针对一项功能
  • CodingStyle-DataEncapsulation:类不应公开实现细节,甚至不向派生类公开

想象一个具有两个不同特征的(不可复制构造的)类。 SRB的原因之一是在Base中实现的。第二个实现于 Derived : public Base 。这两个类都将其数据保存在可 move 的容器中,例如 std::list<std::string> ,称为 m_dataDerivedm_dataBase分别。数据封装的原因m_dataBase应该是private: .

这导致了如何为派生类实现 move-Constructor 的问题。要么:

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

这在语法上违反了第一条规则,即不访问 rhs之后std::move(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_dataBaseprotected:是什么破坏了数据封装。而且Base的每一次变化必须在所有派生的 move 构造函数中完成,这会导致维护问题。

缺少的是一些std::move(Base部分rhs) 。有没有办法做到这一点?

首选的编译示例位于 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))
{}

这段代码是正确且惯用的。如果需要,您可以以明确如何仅 move Base 部分的方式编写它:

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

我们仅 move DerivedBase 子对象。事实上,在调用 Base move 构造函数后,rhsBase 子对象处于有效但(按照惯例)未定义的状态,因此我们最好不要对此做出任何假设。但我们显然没有触及 m_dataDerived,因此之后就可以离开它。

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

您的第一条规则(“我了解到,std::move 不执行任何操作,但在 std::move(someDObject) 之后,对象someDObject 可能会被蚕食,您不应该访问 someDObject") 也是不准确的:

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

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

这当然与 move 构建不太相关,但了解到底发生了什么是值得的。

关于c++ - 如何为具有私有(private)成员的派生类实现 move 构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59323277/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com