当我阅读一些文章时,右值引用和移动语义通常是一起描述的。然而,据我所知,右值引用只是对右值的引用,与移动语义无关。甚至不需要使用右值引用就可以实现移动语义。所以问题是,为什么移动构造函数/运算符=使用右值引用?只是为了让编写代码变得更容易吗?
发布于 2013-03-10 18:05:50
考虑一下这个问题。我们想要支持两个基本的移动操作:移动“构造”和移动“赋值”。我在那里使用引号,因为我们不一定要用构造函数或移动赋值操作符来实现它们;我们可以使用其他东西。
移动“构造”意味着通过从现有对象转移内容来创建新对象,这样删除旧对象不会释放新对象中当前使用的资源。移动“分配”意味着获取一个预先存在的对象并从一个现有对象转移内容,这样删除旧对象不会释放新对象中现在使用的资源。
好的,这些就是我们想要做的操作。那么,怎么做呢?
采取行动“建设”。虽然我们不需要通过构造函数调用来实现这一点,但我们真的很想这样做。我们不想强迫人们做两个阶段的移动构造,即使它是在一些神奇的函数调用之后。因此,我们希望能够以构造函数的形式实现移动。好吧,好吧。
下面是问题1:构造函数没有名称。因此,您只能根据参数类型和重载解析来区分它们。我们知道类型为T
的对象的移动构造函数必须接受类型为T
的对象作为参数。由于它只需要一个参数,因此它看起来完全像一个复制构造函数。
好了,现在我们需要一些方法来满足过载。我们可以引入一些标准库类型,一个std::move_ref
。它将类似于std::reference_wrapper
,但它将是一个不同的类型。因此,您可以说move构造函数是一个接受std::move_ref<T>
的构造函数。好吧,好吧:问题解决了。
但事实并非如此;我们现在有了新的问题。考虑下面的代码:
std::string MakeAString() { return std::string("foo"); }
std::string data = MakeAString();
忽略省略,C++11的表达式值类别规则规定,通过值从函数返回的类型是prvalue。因此,只要有可能,移动构造函数/赋值操作符就会自动使用它。不需要std::move
或类似的东西。
要按照自己的方式来做,需要这样做:
std::string MakeAString() { return std::move(std::string("foo")); }
std::string data = std::move(MakeAString());
这两个std::move
调用都需要避免复制。您必须移出临时并进入返回值,然后移出返回值并进入data
(同样,忽略省略)。
如果你认为这只是一个小麻烦,想想rvalue引用还能给我们带来什么:完美的转发。如果没有特殊的引用折叠规则,您就无法编写正确的转发函数来完美地转发、复制和移动语义。std::move_ref
将是一个真正的C++类型;您不能像使用右值引用那样在它上面添加任意的规则,比如引用折叠。
归根结底,您需要某种语言构造,而不仅仅是库类型。通过将其设置为一种新的引用,您可以定义新的规则来定义哪些内容可以绑定到该引用(哪些内容不能绑定)。您可以定义特殊的引用折叠规则,使完美转发成为可能。
发布于 2013-03-10 17:29:59
两者之间的联系是,从右值移动是安全的(因为(在没有强制转换的情况下)右值引用的是生命周期结束的对象),因此可以通过从被引用的对象窃取/移动来安全地实现接受右值引用的构造函数。
从C++语言的角度来看,这是连接的结束,但是标准库通过始终如一地从左值复制构造和从右值构造移动,以及通过提供助手函数(例如std::move
),使得选择是移动还是复制特定对象(通过在导致复制/移动的表达式中改变对象的值类别),进一步扩展了这种连接。
移动语义可以在没有rvalue引用的情况下实现,但它会不那么整洁。需要解决一些问题:
https://stackoverflow.com/questions/15320749
复制相似问题