提问人:Zebrafish 提问时间:10/5/2023 最后编辑:Zebrafish 更新时间:10/5/2023 访问量:110
为什么命名返回值优化在这里不起作用?
Why isn't named return value optimization working here?
问:
我知道我已经删除了复制构造函数,我假设这没问题,因为我期待命名返回值优化并且会发生直接初始化。是否需要声明复制构造函数?如果我这样做了,我就会遇到一个问题,即班级的成员也无法被复制,那么我该怎么办?
class UniqueBufferPointer
{public:
UniqueBufferPointer() {}
UniqueBufferPointer(const UniqueBufferPointer& other) = delete;
UniqueBufferPointer(UniqueBufferPointer&& other) {}
UniqueBufferPointer& operator=(const UniqueBufferPointer&) = delete;
};
struct GFXAPIImage
{
GFXAPIImage() {}
UniqueBufferPointer handle;
GFXAPIImage(const GFXAPIImage&) = delete;
//GFXAPIImage(const GFXAPIImage& other) = default; // If I use this instead of the compiler complaining that it can't access the copy constructor of this class, it complains it can't access the copy constructor of UniqueBufferPointer.
};
GFXAPIImage func()
{
GFXAPIImage f;
return f; //GFXAPIImage(const GFXAPIImage&) cannot be referenced, it is a deleted function
}
int main()
{
GFXAPIImage f = func();
}
我在Visual STudio上并使用/O2优化标志进行编译。
编辑:此外,如果我为GFXAPIImage声明一个移动构造函数,它就会编译。这是否意味着它使用 move 构造函数?为什么?这是一个 l 值,它不应该有业务移动它。
答:
命名返回值优化是一种非强制性的复制省略。这意味着返回的对象必须具有可访问的、未删除的复制或移动构造函数。
引用 cppreference.com:
非强制复制/移动(自 C++11 起)省略
在以下情况下,编译器是允许的,但不是必需的,即使复制/移动(自 C++11 起)构造函数和析构函数具有可观察到的副作用,编译器也不需要省略类对象的复制和移动(自 C++11 起)构造。这些对象被直接构造到存储中,否则它们将被复制/移动到该存储中。这是一个优化:即使它发生了并且复制/移动(因为 C++11)构造函数没有被调用,它仍然必须存在和可访问(好像根本没有发生优化),否则程序格式不正确:
- 在 return 语句中,当操作数是具有自动存储持续时间的非易失性对象的名称时,该对象不是函数参数或 catch 子句参数,并且与函数返回类型具有相同的类类型(忽略 cv-qualification)。这种复制省略的变体称为 NRVO,即“命名返回值优化”。
引用标准 [class.copy.elision]/1
:
当满足某些条件时,允许实现省略复制/移动构造 [...]
- 在具有类返回类型的函数的 RETURN 语句中,当表达式是具有自动存储持续时间的非易失性对象的名称时 [...]
通过显式删除 的 copy 构造函数,可以防止编译器提供隐式定义的默认移动构造函数。GFXAPIImage
这是一个问题,因为当你写 ,对象需要被复制或移动。但是如果没有移动构造函数,就无法移动它,并且删除了复制构造函数,则无法复制它。Boom:编译器错误。return f
f
请注意,NVRO在这里实际上并不相关。NVRO 的含义是,当从函数返回命名对象时,允许编译器选择性地省略对的调用。但这只是一个优化。你仍然需要首先存在,代码才能形成良好的格式。GFXAPIImage::GFXAPIImage(GFXAPIImage&&)
GFXAPIImage::GFXAPIImage(GFXAPIImage&&)
要修复代码,只需从 中删除显式删除的复制构造函数即可。这将使编译器能够提供隐式定义的默认移动构造函数,这将使移动再次可移动。或者,您可以改为定义自己的移动构造函数,或者请求编译器提供默认的移动构造函数,即使删除了带有 .GFXAPIImage
GFXAPIImage
= default
请注意,即使没有显式删除复制构造函数,它仍会被隐式删除。因此,您不会因省略显式删除而失去任何安全性。
另请参阅自动生成默认/复制/移动 ctor 和复制/移动赋值运算符的条件?
struct GFXAPIImage
{
GFXAPIImage() {}
UniqueBufferPointer handle;
// no explicitly deleted copy constructor
};
GFXAPIImage func()
{
GFXAPIImage f;
return f; // OKAY, calls move constructor
}
int main()
{
GFXAPIImage f = func();
// GFXAPIImage g = f; // would fail due to GFXAPIImage(const GFXAPIImage&) being implicitly deleted
}
评论
std::vector
评论
public: