【问题标题】:lvalue vs rvalue dubious左值与右值可疑
【发布时间】:2015-08-20 13:35:10
【问题描述】:

以下代码运行良好,但据我了解,它不应该

#include <iostream>
#include <vector>

struct Data
{
  explicit Data():value(1){}
  int value;
};

struct Foo
{
  explicit Foo(Data& data):data_(data){}

  inline void print() const
  {
    std::cout<<data_.value<<std::endl;
  }
  Data& data_;
};

void addEntry(std::vector<Foo>& vec)
{
  Data data;
  Foo foo(data);
  vec.push_back(foo);
}

int main()
{
  std::vector<Foo> vec;
  addEntry(vec);
  vec[0].print();
}

函数addEnty 创建一个名为dataData 实例。然后创建Foo 的实例,称为foo,其中存储了对data 的引用。然后将这个距离复制到向量vec 中。因此,当函数结束时,vec[0] 应该包含一个悬空引用,因为 data 已被销毁。我对吗?所以我希望得到一些垃圾调用方法print()。我获得正确的值 1 是偶然还是我错过了什么?

为了使其正确,我会移动数据以避免悬空引用。所以我会修改构造函数

explicit Foo(Data&& data):data_(data){}

和函数

Foo foo(std::move(data));

这样foo,因此它在vec[0] 中的副本包含实例data,而不是对它的引用。我对吗?这是正确的解决方案吗?这样Foo::data_需要是Data类型还是Data&amp;类型?

【问题讨论】:

    标签: c++ c++11 move lvalue rvalue


    【解决方案1】:

    是的 Foo 将持有一个悬空引用。 Foo 类应该包含 Data 而不是 Data& 或 Data&&。

    #include <iostream>
    #include <vector>
    
    struct Data
    {
      explicit Data():value(1){}
      int value;
    };
    
    struct Foo
    {
      // this is needed if you want to pass lvalue
      Foo(const Data& data):data_(data) 
      {}
      // for rvalue
      Foo(Data&& data):data_(std::move(data))
      {}
    
      void print() const
      {
        std::cout<<data_.value<<std::endl;
      }
    
      Data data_;
    };
    
    void addEntry(std::vector<Foo>& vec)
    {
      vec.emplace_back(Foo(Data())); 
    
      // or
    
      Data data;
      // do somth with data
      vec.emplace_back(Foo(std::move(data)));        
    
      // or
    
      Data data;
      // do somth with data
      Foo foo {std::move(data)};
      // do somth with foo, but
      // do not use data here!!!
      vec.push_back(std::move(foo));        
    }
    
    int main()
    {
      std::vector<Foo> vec;
      addEntry(vec);
      vec[0].print();
    }
    

    【讨论】:

    • 如果在传递data构造对象时已经需要调用std::move,为什么还要在构造函数Foo(Data&amp;&amp;)的初始化列表中调用std::move
    • 因为 std::move 不会在任何地方移动任何东西,只是将左值转换为右值,即使移动构造函数中的参数声明为Data&amp;&amp;,表达式data 也是左值,需要转换以右值调用 Data 类的移动构造函数
    • 是的,我知道它只会将左值转换为右值。但是当我调用Foo (std::move(data)) 时,我将其转换为右值,因此我不需要在初始化列表中使用data_(std::move(data))。我什么时候需要这种转换?在我看来,它从未在您的代码中使用过。只是为了更好地理解这些右值:)
    【解决方案2】:

    正如您所建议的,由于悬空引用,您的示例代码具有未定义的行为。您看到的行为纯属偶然。

    一个采用右值引用的函数说“我要从你传入的任何东西中窃取数据”。只要这些是您的语义,让构造函数获取这样的引用就可以了,但您的示例似乎并非如此。

    一种可能性是按值获取参数,然后将其移动到成员变量中:

    struct Foo
    {
      explicit Foo(Data data):data_(std::move(data)){}
      Data data_;
    };
    

    这样,客户端代码可以传递左值(1 次复制,1 次移动)或右值(2 次移动)。只维护一个构造函数很方便,但如果 Data 的移动成本很高,这可能效率低下。

    其他可能性是让一个构造函数获取转发引用,或者为右值维护一个重载,为左值维护一个重载。

    【讨论】:

      【解决方案3】:

      你是对的,这是偶然的,这实际上属于未定义的行为。
      字段应为 Data,输入 Foo 以避免悬空引用。
      你可以这样重写:

      #include <iostream>
      #include <vector>
      
      struct Data
      {
        explicit Data():value(1){}
        int value;
      };
      
      struct Foo
      {
        explicit Foo(Data&& data):data_(std::move(data)){}
      
        inline void print() const
        {
          std::cout<<data_.value<<std::endl;
        }
        Data data_;
      };
      
      void addEntry(std::vector<Foo>& vec)
      {
        vec.emplace_back(Foo(Data()));
      }
      
      int main()
      {
        std::vector<Foo> vec;
        addEntry(vec);
        vec[0].print();
      }
      

      【讨论】:

        猜你喜欢
        • 2019-04-09
        • 2021-07-01
        • 1970-01-01
        • 2011-02-14
        • 1970-01-01
        • 1970-01-01
        • 2016-04-22
        • 2011-01-18
        • 2017-11-24
        相关资源
        最近更新 更多