rvalue ссылка и перемещение локальной переменной - PullRequest
5 голосов
/ 19 октября 2019

В этом фрагменте я выделяю локальный объект B, который передаю конструктору другого объекта C, который принимает его как ссылку на значение. Затем я помещаю последний в контейнер.

Когда я получаю C и static_cast его член обратно в B, значение неверно (и valgrind идентифицирует 18 ошибок!)

#include <iostream>
#include <memory>
#include <vector>

class A {
public:
  virtual ~A() {};
};

class B: public A {
public:
  B(int foo) : _foo(foo) {}
  int _foo;
};

class C {
public:
  C(A&& a): _a(std::move(a)) {}
  A&& _a;
};

std::vector<C> v;

void bar() {
  v.emplace_back(B(12));
}

int main() {
  bar();
  C&& c = std::move(v.front());
  std::cout << static_cast<B&&>(c._a)._foo << std::endl;
  return 0;
}

Мое пониманиеявляется то, что, поскольку C принимает ссылку на rvalue, не должно быть никакого среза объекта. Я все еще должен быть в состоянии получить полный объект B. Кроме того, мое понимание (наиболее вероятно ошибочное) std::move заключается в том, что я могу «переместить» локальную переменную из ее контекста и что, взяв ссылку на rvalue, C становится владельцем B.

39931504 вместо 12.

Кто-нибудь может мне объяснить, что происходит?

Ответы [ 2 ]

2 голосов
/ 19 октября 2019

Я все еще смогу получить полный объект B.

Вы были бы, если бы это был не временный объект, который не существует к моменту, когда элемент управления достигнет bar 'Закрывающая скобка. В вашем C содержится ссылка, и ссылка на временный объект может быстро стать висячей.

Быстрое решение состоит в том, чтобы просто изменить объявление члена C на std::unique_ptr<A> a; и правильно инициализировать его,скажем, как

template<class AChild> C(AChild child)
  : a(std::make_unique<AChild>(std::move(child))) {};

(добавить SFINAE по вкусу).

1 голос
/ 19 октября 2019

Проблема в том, что ссылка на A в классе C (т. Е. Элемент данных _a) переживает объект, на который она ссылается. Это связано с тем, что временное B(12) в вашей bar() функции

void bar() {
   v.emplace_back(B(12));
}

не существует после возврата из bar(). Таким образом, ссылка на A, содержащаяся в классе C, становится висячей ссылкой .


Вместо временного вы можете создать объект B сnew оператор:

void bar() {
   B* ptr = new B(12);
   v.emplace_back(std::move(*ptr));
}

Этот B объект не перестает существовать при возврате из bar(), поэтому ссылка на A в C остается действительной.

Обратите внимание, что при таком подходе вам необходимо уничтожить объект вручную:

C&& c = std::move(v.front());
// ...
delete &c._a; // destroy object
...