【问题标题】:getting invalid ouput after std::move of an element from std::list从 std::list 中的元素的 std::move 后得到无效输出
【发布时间】:2021-11-06 22:11:55
【问题描述】:

我正在尝试了解 std::move。在我的代码中,我从std::list<struct Data> 移动一个元素,其中struct Data 内部包含两个std::string 字段,但我没有得到预期的输出。这是我的代码:

#include <iostream>
#include <string>
#include <list>

struct Data {
    std::string topic {};
    std::string msg {};

    Data(const std::string& topic, const std::string& msg) {
        this->topic = topic;
        this->msg = msg;
    }
};

int main() {
    std::list<Data> data_list;
    data_list.push_back(Data("A", std::string(1000, 'a')));
    data_list.push_back(Data("B", std::string(1000, 'b')));
    data_list.push_back(Data("C", std::string(1000, 'c')));
    data_list.push_back(Data("D", std::string(1000, 'd')));
    data_list.push_back(Data("E", std::string(1000, 'e')));
    
    while (!data_list.empty()) {
        std::cout << (void*)&data_list.front() << "\n";
        Data&& d1 = std::move(data_list.front());
        data_list.pop_front();
        std::cout << d1.topic << ", " << d1.msg << "\n";
        std::cout << (void*)&d1 << "\n\n";
    }
    std::cout << std::endl;
}

【问题讨论】:

  • 您存储对第一个元素的引用,然后弹出第一个元素。你会得到一个悬空的引用和未定义的行为。
  • 您无法移动此特定实例。但是,如果您像 NathanOliver 在他的好答案中所做的那样,隐式 move-ctor 会将两个字符串成员管理的内存移动到新实例(而不是复制内存)。是的,std::move 使用起来并不直观 :)
  • 考虑使用emplace_back(),像这样:data_list.emplace_back("A", std::string(1000, 'a'))

标签: c++ stdlist stdmove


【解决方案1】:

这里的问题是你实际上并没有移动任何东西。当您调用std::move 时,实际上没有任何移动。它所做的是将您拥有的左值转换为右值,以便它可以被移动构造或移动分配。这不是你在这里做的。你用

Data&& d1 = std::move(data_list.front());

它有d1 作为右值引用,这意味着不再移动,它只是对列表中对象的引用。当您弹出该元素时,您的引用现在引用一个不再存在的对象,并且使用它具有未定义的行为,导致您看到的输出。如果要移动元素,则需要

Data d1 = std::move(data_list.front());

现在d1 将使用Data 的隐式移动构造函数将列表元素移动到d1

【讨论】:

  • Data d1 = std::move(data_list.front()); 语句不会创建std::list 元素的副本?
  • @Harry 不,它会将data_list.front() 移动到d1。你的数据已经有了。 std::string 是一个类类型。它也有一个移动构造函数。既然这样,编译器生成的 Data 的移动构造函数也将调用字符串的移动构造函数。这可能会为您解决问题:stackoverflow.com/questions/3106110/what-is-move-semantics
  • 如果结构数据又包含另一个用户定义的结构作为字段,它既不实现移动构造函数也不实现移动赋值运算符?
  • @Harry 刚刚看到您更新的第二条评论。在这种情况下,如果无法移动,它会退回到副本。如果它也无法处理,那么你会得到一个编译器错误。
  • 是的,像int 这样的东西不会从移动中受益,它仍然是一个副本。当类指向它的数据(如向量)时,移动会给你带来提升。当你拥有它时,移动只是意味着将指针复制到目标,然后将源设置为空,这样它就不再指向数据。这使您可以将指向的数据从一个对象“移动”到另一个对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-25
  • 1970-01-01
  • 2016-08-16
  • 1970-01-01
  • 2018-02-13
  • 1970-01-01
  • 2020-05-27
相关资源
最近更新 更多