【问题标题】:C++ vector move semanticsC++ 向量移动语义
【发布时间】:2021-07-24 04:15:11
【问题描述】:

这是我的情况:

我有这样的课:

class JSON {
private:
std::vector<JSON> children;
...
public:
JSON& operator[](std::string propertyName);
...
}

如何应用移动语义?如果使用它们有什么不同吗?

我觉得如果我这样做了

std::swap(json["hello"], json["world"])

2 个 JSON& 对象的交换将与深拷贝一样无效,因为 JSON 类中不涉及指针。

据我了解,如果我想有效地使用 Move Semantics,我需要一个指向我的子向量的指针到 std::move 该指针毫不费力,我是否正确地说如果我的类没有指针, 对 std::swap 没用?

std::swap 在我的情况下会这样做吗?

temp = json1.children;           // by value? deep copy of std::vector?
json1.children = json2.children; // by value? deep copy of std::vector?
json2.children = temp;           // by value? deep copy of std::vector?

这里有一点上下文: 我通过套接字接收文本,将文本转换为 JSON,我使用 json["type"] 获取消息类型,然后我的目标是向订阅该消息类型的订阅者发送包含 json["data"]std::shared_ptr&lt;const JSON&gt;。所以我这样做了:

JSON* dummy = new JSON();
std::swap(*dummy, json["data"]);
std::shared_ptr<const JSON> dataPtr(dummy);

如果我不这样做而只做std::shared_ptr&lt;const JSON&gt;(&amp;json["data"]),那么超出范围的共享指针和JSON 都会尝试释放json["data"](程序失败)。如果您使用std::make_shared&lt;JSON&gt;(json["data"]),您将使用 JSON& 调用复制构造函数,从而进行深层复制。

我希望我的意图很明确,我只是想避免深度复制JSONstd::vector&lt;JSON&gt;

【问题讨论】:

  • std::vector 已经具有移动语义。它内部一般只需要swap 3 个指针或一个指针和2 个整数。您不需要自己实现移动语义。编译器生成的移动构造函数和赋值运算符在这种情况下可以正常工作。
  • 一种方法是将 JSON 分配和存储与 JSON 对象分离。 Document 结构可以存储所有对象并存储有关文档结构的信息(可以是完全扁平的)并使用JSON 作为文档的用户友好视图。

标签: c++ move semantics


【解决方案1】:

据我了解,如果我想有效地使用 Move Semantics,我需要一个指向我的子向量的指针到 std::move 该指针毫不费力,我是否正确地说如果我的类没有指针, 对 std::swap 没用?

不,一般情况下并非如此,标准库容器类将具有移动构造函数,只要您知道如何调用它们,它们就会执行您想要的操作,这将我们带到您问题的第二部分:

std::swap 在我的情况下会这样做吗?

我知道得到“视情况而定”的答案真的很令人沮丧,但与 C++ 中的大多数事情一样,这真的取决于。

要以最一般的方式回答您的问题,是的,std::swap() 可能会在大多数情况下做您想做的事情,尤其是当您只是使用标准库容器类时。事情变得奇怪的地方(而且我没有足够的信息给你一个完整的答案)是你定义了自己的类,这里只显示了其中的一部分。魔鬼在细节中,因此程序的实际行为将取决于这些省略号中的内容。

一般来说,当您试图了解复制/移动行为的预期结果时,您确实需要考虑构造函数。假设您使用的是 C++ 的“现代”(即 11 后版本),std::swap() 函数将大致如下所示:

template<typename T> void swap(T& t1, T& t2) {
    T temp = std::move(t1); // or T temp(std::move(t1));
    t1 = std::move(t2);
    t2 = std::move(temp);
}

另见this 相关帖子。更具体地说,对于您的示例,模板实例化将如下所示:

void swap(JSON& t1, JSON& t2) {
    JSON temp = std::move(t1); // or T temp(std::move(t1));
    t1 = std::move(t2);
    t2 = std::move(temp);
}

请记住,std::move() 实际上只是一种通过一些极端情况处理将左值引用转换为右值引用的奇特方式。函数本身不做任何事情,它是一种告诉编译器如何执行重载解析的方法。

所以现在问题变成了:当编译器需要从右值引用构造一个 JSON 对象到对象类型JSON 时会发生什么?这个问题的答案取决于类上可用的构造函数,其中一些可能由编译器隐式生成。另请参阅this 帖子。

编译器将为操作选择最合适的构造函数,这可能是一个隐式构造函数,并且取决于您在类上声明的内容,实际上可能不是 this 示例中解释的移动构造函数。为了避免落入这个陷阱,您需要知道右值引用可以绑定到const 左值引用,因此具有以下签名的复制构造函数:

    JSON(const JSON &);

在某些情况下是std::move() 操作左侧的有效重载候选。这可能就是为什么您有时会听到人们说std::move()“实际上并没有移动任何东西”,或者它“仍然只是在复制”。

那么所有这些将您的代码留在哪里?基本上,如果你没有用户声明的构造函数,并且你让编译器为你做这件事,那么std::swap 可能会按照你想要的方式移动所有成员的内存。一旦您开始声明自己的构造函数,情况就会变得更加复杂,我们必须谈谈具体细节。

作为一个小后记,你真的需要使用swap()吗?看起来你只是想为一个用另一个对象的内容初始化的对象构造一个shared_ptr。这可能是一种稍微简单的方法:

  std::shared_ptr<const JSON> outPtr = std::make_shared<JSON>(std::move(json["data"]));

这将使用移动构造函数构造一个 JSON 类型的对象(假设它是考虑到我提到的警告的最佳重载候选对象)并返回一个 shared_ptr 给它。

【讨论】:

  • 我会尝试std::move(json["data"]),但我认为 move 会释放那里的内存,弄乱 json 析构函数也会释放内存
  • @BrunoCL 当你这样做时,它确实会移出json["data"] 进入你将分配给它的东西。但是json["data"] 的值甚至不会为空,只是一个未指定的状态。我不会那样做,因为这很危险。我更喜欢std::exchange(json["data"], {})。交换会将默认构造值放回json["data"] 对象中。
  • @BrunoCL 只是为了非常清楚,移动不会释放内存,从语义上讲它会改变所有权。 @Guillaume 说的当然是真的。在std::move() 之后,原始json["data"] 对象的状态是未定义的,不得使用。所有权由outPtr 指向的对象取得,我认为这是您想要的。 C++ 允许你做很多为了提高效率而被认为是危险的事情。如何选择使用(或不使用)这些工具取决于您。
猜你喜欢
  • 2023-03-29
  • 2016-11-11
  • 2020-12-23
  • 2019-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-04
相关资源
最近更新 更多