Search code examples
c++c++11movelvaluervalue

lvalue vs rvalue dubious


The following code run fine but, for what I understand, it shouldn't

#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();
}

The function addEnty create an instance of Data called data. Then creates an instance of Foo, called foo, which stores a reference to data. This istance is then copyed inside the vector vec. Therefore, when the function end, vec[0] should contain a dangling reference since data is destroyed. Am I right? So I would expect to obtain some garbage calling the method print(). Is it only by chance that I obtain the right value 1 or am I missing something?

To make it correct, I would move data in order to avoid the dangling reference. So I would modify the constructor with

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

and the function with

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

In this way foo, and consequently its copy inside vec[0], contains the instance data instead of a reference to it. Am I right? Is it the correct solution? In this way, Foo::data_ needs to be of type Data or of type Data&?


Solution

  • Yes Foo will hold a dangling reference. Foo class should hold Data not Data& or 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();
    }