【问题标题】:rvalue reference and move of a local variable局部变量的右值引用和移动
【发布时间】:2020-02-16 05:27:03
【问题描述】:

在这个 sn-p 中,我分配了一个本地对象 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 采用右值引用,因此不应该有任何对象切片。我仍然应该能够检索到完整的 B 对象。此外,我对std::move 的理解(很可能是有缺陷的)是我可以将局部变量“移出”其上下文,并且通过获取右值引用,C 获得了 B 的所有权。

输出是39931504 而不是12

谁能给我解释一下这是怎么回事?

【问题讨论】:

  • C 有一个右值引用作为成员。所以它不拥有任何东西,它只是引用一个变量。该变量是函数bar 中的局部变量,这意味着它会在函数结束后立即销毁。

标签: c++ move move-semantics rvalue-reference


【解决方案1】:

我应该仍然能够检索完整的 B 对象。

如果它不是在控制到达bar 的右大括号时不存在的临时文件,那么您会是。您在 C 中持有一个引用,而对临时对象的引用很快就会变得悬空。

快速解决方法是将C 的成员声明更改为std::unique_ptr&lt;A&gt; a; 并正确初始化它,例如

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

(根据口味添加 SFINAE)。

【讨论】:

  • 由于 OP 似乎想要B 的多态行为,将成员变为std::unique_ptr&lt;A&gt; 似乎更合适。
  • 是的@super,我试图避免对象切片。我想我当时不明白右值引用的目的。我认为它们被用来转移“临时”变量的所有权。但也许我混淆了右值和本地左值......
  • @LukeSkywalker 他们可以用来传递一个临时的。这里的问题是你将临时文件一直传递给v,但是一旦你有对临时文件的引用,你需要将它实际存储在某个地方。如果没有,它只会被销毁。
  • @LukeSkywalker 不要过度复杂化。使用引用,如果必须,使用指针。如果您发现自己在模板之外编写&amp;&amp;,请停止。你可能走错了方向。如果您仍然认为自己正朝着正确的方向前进,请观看此视频并问问自己您是否仍然这么认为。 channel9.msdn.com/Series/…
  • @LukeSkywalker 转让所有权的概念基本上可以让你move-construct一个新对象并传递一些右值(你的临时)作为移动构造函数的参数。在您的情况下没有新对象,您只需将引用绑定到一个临时对象,它本身不会在任何地方传输任何内容。
【解决方案2】:

问题是C 类中对A 的引用(即数据成员_a)比它所引用的对象的寿命更长。这是因为您的bar() 函数中的临时 B(12)

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

bar() 返回后不存在。因此,C 类包含的对A 的引用变成了一个悬空引用


您可以使用new 运算符创建B 对象,而不是临时对象:

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

bar() 返回时,此B 对象不会停止存在,因此C 中对A 的引用仍然有效。

请注意,使用这种方法您需要手动销毁对象:

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

【讨论】:

  • 但需要稍后清理。
  • 你为什么在(几乎)2020 年在 C++ 中使用 raw new 和 delete?
  • @Taekahn 解释问题,而无需更改 OP 对类 C 的定义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-16
  • 1970-01-01
  • 2023-04-04
  • 1970-01-01
  • 2013-08-02
  • 2011-11-06
相关资源
最近更新 更多