【问题标题】:C++ move semantics and built-in typesC++ 移动语义和内置类型
【发布时间】:2017-10-10 13:01:09
【问题描述】:

我正在尝试使用以下代码来理解移动语义。

#include <iostream>
#include <utility>
using namespace std;

class A 
{
   public:
   int a;
   A():a{1}{}
   A(A&& rref):a{rref.a}{cout<<"move constructor called"<<endl;}
   A(const A& ref):a{ref.a}{cout<<"copy constructor"<<endl;}
};

int main(){

  A original; // original object

  cout<<"original.a = "<<original.a<< "|  address original.a="<< &(original.a)<<endl;

  A movedto (std::move(original)); // calls A(A&&)

  cout<<"original.a = "<<original.a<< "| address original.a"<< &(original.a)<<endl;
  cout<<"movedto.a = "<<movedto.a<<"| address movedto.a"<< &(movedto.a)<<endl;

  return 0;
}

给出以下输出

original.a = 1|  address original.a=0x7fff1611b6d0
move constructor called
original.a = 1| address original.a0x7fff1611b6d0
movedto.a = 1| address movedto.a0x7fff1611b6e0

可以看出original.amovedto.a的地址不同,所以a成员在A(A&amp;&amp; rref):a{rref.a}进行了复制操作。

我知道 move 对于内置类型会降级为 copy。我的问题是如果我想劫持(而不是复制)这个类的一个实例,我该怎么办。假设我有 100 个(而不是只有一个)内置类型的成员,这会使复制变得昂贵。

一种明显的方法是将对象存储在堆上并使用引用语义来传递它。但是希望保留值语义并且仍然能够规避复制。

【问题讨论】:

  • 复制 100 个ints 仍然不贵。但是您可以使用std::vector&lt;int&gt; 成员并移动它。它会为你处理一切。
  • 两个问题:1) std::vector&lt;T&gt; 是否具有值语义? 2)如果1的答案是肯定的,vector&lt;T&gt;是做什么的?
  • a{rref.a} 应该是 a{std::move(rref.a)} 才能实际移动 a(尽管它仍然会复制,因为它与 int 相同)。
  • 您在这里使用内置类型这一事实无关紧要。不管你做什么类型aoriginal.amovedto.a仍然会有不同的地址。
  • 不,@NathanOliver 是正确的,rref.a 是左值,因为 rref 是左值(命名为右值引用)。如果您希望它成为 xvalue,则需要 std::move(rref.a)。或者,如果你已经完成了std::move(rref).a,那么它也可以工作,因为. 运算符将从右值提供一个右值。

标签: c++ move-semantics


【解决方案1】:

这是不可能的和/或没有意义的。


我们从:

A original; // original object

问题的主要部分是:

如果我想劫持(而不是复制)类的一个实例,我该怎么办 比如这个?

所以这意味着我们最终会得到:

A movedto; // new object that has all of original's members

但这里需要注意的是,我们想要“规避复制”并且我们不想使用引用或指针,即“引用语义”,只有“堆栈”或“值语义”。

如果我们希望movedto 在已分配的相同内存位置拥有相同的成员,那么我们可以创建对original 的引用:

A& movedto{original}; // references members at the same memory locations. 

但是这个问题的一部分表明我们没有使用引用,因为大概我们希望这个对象有不同的生命周期。因此,如果我们想保持original 的成员“活着”并分配到当前块的末尾之外,那么我们会立即发现我们无法控制该底层内存。


在这个问题中original 是一个具有自动存储持续时间的对象。具有自动存储持续时间的对象根据其范围自动管理其生命周期。编译器可能已经使用堆栈来存储它,并且编译器可能使用堆栈指针,每次添加对象时都会向下移动,但 C++ 标准没有指定应该如何完成。我们确实知道该标准规定具有自动存储持续时间的对象将按照它们在作用域结束时创建的相反顺序被销毁。

因此,试图控制创建具有自动存储持续时间的对象的位置没有意义,将此类对象的成员分配给另一个对象也没有意义。内存是自动分配的。

如果我们想重用已经分配为具有自动存储持续时间(堆栈/值语义)的对象的一部分的变量,那么我们使用的内存将在该对象的生命周期结束时被释放。我们必须为此使用动态存储(即“堆”或“引用语义”)。

【讨论】:

  • 这两种方法都将使用堆来存储实际数据,因此在顶部它看起来像值语义,但在内部我们正在交换指针以实现移动。这使我相信以下内容。如果我错了,请纠正我。 1. 数据应该始终驻留在带有管理句柄对象(unique_ptr 或 Impl*)的堆上,我们在该对象上实现移动构造函数和赋值。
  • @Dhruv 是的,两者都使用动态存储持续时间(堆存储)。是的,我看不到在为具有自动存储持续时间(堆栈)的对象分配的内存中执行此操作的方法。我已经更新了答案,以关注问题的主要要点。
  • 知道了。我们已经偏离了问题的主题,但我认为这就是我想要的。谢谢
猜你喜欢
  • 2013-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-11
  • 2015-07-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多