gex*_*ide 3 c++ optimization return-value-optimization as-if
考虑以下代码(godbolt):
#include <optional>
#include <array>
struct LargeType {
std::array<int, 256> largeContents;
};
LargeType doSomething();
std::optional<LargeType> wrapIntoOptional(){
return std::optional<LargeType> {doSomething()};
}
Run Code Online (Sandbox Code Playgroud)
如您所见,有一个函数返回一个大 POD,然后有一个函数将其包装到std::optional. 正如在 godbolt 中可见,编译器memcpy在此处创建了一个,因此它不能完全避免移动对象。为什么是这样?
如果我理解正确的话,C++ 语言将允许由于假设规则而省略移动,因为它没有明显的副作用。看来编译器确实无法避免。但为什么?
我(可能不正确)的理解是编译器如何优化输出,memcpy将对可选内部存储的引用传递给doSomething()(因为我猜这么大的对象无论如何都会通过隐藏引用传递)。wrapIntoOptional由于 RVO,可选本身已经位于调用者的堆栈上。由于 的构造函数的定义std::optional位于标头中,因此编译器可以使用它,因此它应该能够内联它,因此它可以doSomething首先将该存储位置传递给它。那么我的直觉出了什么问题呢?
澄清一下:我并不认为 C++ 语言要求编译器内联它。我只是认为这将是一个合理的优化,并且考虑到将事物包装到可选值中是一种常见的操作,这将是在现代编译器中实现的优化。
不可能通过构造函数调用来消除对构造对象管理的存储的任何复制/移动。
构造函数将对象作为引用。为了将引用绑定到某个对象,必须有一个对象,因此doSomething()必须将纯右值具体化为临时对象以绑定到引用,然后构造函数必须从该临时对象复制/移动到其自己的存储中。
不可能通过函数参数来省略。这需要了解函数的实现以及 C++ 的指定方式,仅了解其他函数的声明(除了常量表达式求值之外)就可以编译每个函数。这会打破这一点,或者需要在声明中使用新类型的注释。
但这并不能阻止编译器以不影响可观察行为的方式进行优化。如果您的编译器没有弄清楚额外的副本是可以避免的,并且在查看所有相关函数/构造函数定义时没有可观察到的副作用,那么您可以向编译器供应商抱怨这一点。复制省略的概念是允许编译器优化复制/移动,即使它会产生明显的副作用。