C++ 向量移动语义

C++ vector move semantics

这是我的情况:

我有一个 class 这样的:

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

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

我觉得如果我这样做

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

2个JSON&对象的交换将和深拷贝一样无效,因为JSON class.

中没有涉及指针

据我所知,如果我想有效地使用移动语义,我需要有一个指向我的子向量的指针来毫不费力地 std::move 该指针,我说得对吗,如果我的 class没有指针,没用到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<const JSON> ] 订阅该消息类型的订阅者。所以我这样做了:

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

如果我不这样做而只做 std::shared_ptr<const JSON>(&json["data"]),则超出范围的共享指针和 JSON 都会尝试释放 json["data"](程序失败)。如果你这样做 std::make_shared<JSON>(json["data"]) 你用 JSON& 调用复制构造函数,这会产生一个深拷贝。

我希望我的意图很明确,我只是想避免深度复制 JSONstd::vector<JSON>

From what I understand, if I wanted to use Move Semantics efficiently, I would need to have a pointer to my vector of children to std::move that pointer effortlessly, am I correct in saying that if my class has no pointers, it is useless to std::swap?

不,这通常不是真的,标准库容器 classes 将具有移动构造函数,只要您知道如何调用它们,它们就会执行您想要的操作,这将我们带到您的第二部分问题:

Does std::swap do this in my case?

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

为了尽可能以最一般的方式回答您的问题,是的 std::swap() 可能大部分时间都会做您想要的,特别是如果您只是使用标准库容器 classes .事情变得奇怪的地方(我没有足够的信息来给你一个完整的答案)是你定义了你自己的 class,这里只显示了它的一部分。细节决定成败,因此程序的实际行为将取决于这些省略号中的内容。

一般来说,当您试图了解 copy/move 行为会发生什么时,您确实需要从构造函数的角度来思考。假设您使用的是“现代”(即 post-11 版本)的 C++,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 相关 post。更具体地说,对于您的示例,模板实例化将如下所示:

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 对象时会发生什么?这个问题的答案取决于 class 上可用的构造函数,其中一些可能由编译器隐式生成。另见 this post.

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

    JSON(const JSON &);
在某些情况下,

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

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

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

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

这将使用移动构造函数构造一个类型为 JSON 的对象(假设它是考虑到我提到的注意事项的最佳重载候选者)并且 return 一个 shared_ptr 到它。