请考虑以下类Buffer
,其中包含一个std::vector
对象:
#include <vector>
#include <cstddef>
class Buffer {
std::vector<std::byte> buf_;
protected:
Buffer(std::byte val): buf_(1024, val) {}
};
现在,考虑下面的功能make_zeroed_buffer()
。类BufferBuilder
是 local class ,它是从Buffer
公开派生的。其目的是创建Buffer
对象。
Buffer make_zeroed_buffer() {
struct BufferBuilder: Buffer {
BufferBuilder(): Buffer(std::byte{0}) {}
};
BufferBuilder buffer;
// ...
return buffer;
}
如果没有进行复制省略,是否保证上方的buffer
对象会被移走?
我的推理如下:
buffer
语句中的表达式return
是 lvalue 。由于它是不再使用的本地对象,因此编译器将其转换为 rvalue 。buffer
对象的类型为BufferBuilder
。 Buffer
是BufferBuilder
的公共基类,因此此BufferBuilder
对象被隐式转换为Buffer
对象。BufferBuilder
的引用到对Buffer
的引用)。对BufferBuilder
的引用是一个右值引用(请参阅1.),它变成对Buffer
的右值引用。Buffer
的右值引用与Buffer
的move构造函数匹配,该构造函数用于构造Buffer
按值返回的make_zeroed_buffer()
对象。结果,通过从对象Buffer
的{{1}}部分开始构造返回值。答案 0 :(得分:7)
RVO优化
如果没有复制省略[...]
实际上,不会出现复制省略(没有 if 的情况下)。
根据C ++标准class.copy.elision#1:
在以下情况下允许使用[...]:
-在具有类返回类型的函数中的return语句中,当表达式是具有相同类型的非易失性自动对象([...])的名称时忽略cv限定)作为函数返回类型[...]
从技术上讲,当您返回派生的类并且进行切片操作时,将无法应用RVO。
从技术上讲,RVO会在堆栈帧的返回空间上构造本地对象。
|--------------|
| local vars |
|--------------|
| return addr |
|--------------|
| return obj |
|--------------|
通常,派生类与其父类可以具有不同的内存布局(大小,对齐方式等)。因此,不能保证可以在为返回的对象( parent )保留的位置中构造本地对象( derived )。
隐式举动
现在,隐式举动怎么办?
是否可以保证将上面的缓冲对象移出??
简而言之:不。相反,可以保证对象将被复制!
在这种特殊情况下,由于切片,将不会执行隐式移动。
简而言之,发生这种情况是因为过载解析失败。
它尝试与移动构造函数(Buffer::Buffer(Buffer&&)
)匹配,而您有BufferBuild
对象)。因此,它依赖于复制构造函数。
根据C ++标准class.copy.elision#3:
[...]如果所选构造函数的第一个参数的类型或return_value重载不是对该对象类型的右值引用(可能是cv限定的),则再次执行重载解析,将对象视为左值。
因此,由于第一个重载解析失败(如上所述),因此该表达式将被视为 lvalue (而不是 rvalue ),从而禁止移动。
亚瑟·奥德怀尔(Arthur O'Dwyer)的一个有趣的演讲特别提到了这个案例。 Youtube Video。
附加说明
在clang上,您可以传递标志-Wmove
以便检测此类问题。
确实for your code:
local variable 'buffer' will be copied despite being returned by name [-Wreturn-std-move]
return buffer;
^~~~~~
<source>:20:11: note: call 'std::move' explicitly to avoid copying
return buffer;
clang直接建议您在返回表达式上使用std::move
。
答案 1 :(得分:-1)
make_zeroed_buffer()中的对象缓冲区将在销毁之后被销毁,并借助Buffers的副本构造函数获取返回值。