假设我有一个类似的功能
using std::vector;
vector<int> build_vector(int n)
{
if (some_condition(n)) return {};
vector<int> out;
for(int x : something())
{
out.push_back(x);
}
return out;
}
函数开头的return {}
是否可以防止NRVO?我很好奇,因为这似乎等同于以下内容:
using std::vector;
vector<int> nrvo_friendly_build_vector(int n)
{
vector<int> out;
if (some_condition(n)) return out;
for(int x : something())
{
out.push_back(x);
}
return out;
}
但是我不清楚在第一种情况下是否允许编译器执行NRVO。
答案 0 :(得分:2)
来自https://en.cppreference.com/w/cpp/language/copy_elision
在以下情况下,允许编译器,但即使复制/移动(自C ++ 11起)构造函数和析构函数也不需要省略类对象的复制和移动(自C ++ 11起)构造有明显的副作用。这些对象直接构造到存储中,否则会将它们复制/移动到其中。这是一种优化:即使发生并且未调用copy / move(由于C ++ 11)构造函数,它仍然必须存在并且可以访问(好像根本没有优化发生),否则该程序会出现问题-形成:
在return语句中,当操作数是具有自动存储持续时间的非易失性对象的名称时,该对象不是函数参数或catch子句参数,并且具有相同的类类型(忽略cv-qualification)作为函数返回类型。复制省略的这种变体被称为NRVO,“命名返回值优化”。
...
提早归还没有任何限制,因此这两个版本均适用于NRVO。
答案 1 :(得分:1)
我实际上是在想同样的事情((尤其是因为该标准不需要“复制省略”) ),所以我在在线编译器中通过将std::vector
替换为a来对其进行了快速测试。窗口小部件结构:
struct Widget
{
int val = 0;
Widget() { printf("default ctor\n"); }
Widget(const Widget&) { printf("copy ctor\n"); }
Widget(Widget&&) { printf("move ctor\n"); }
Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
Widget& operator=(Widget&&) { printf("move assign\n"); return *this; }
~Widget() { printf("dtor\n"); }
void method(int)
{
printf("method\n");
}
};
V1使用build_vector()
:http://coliru.stacked-crooked.com/a/5e55efe46bfe32f5
#include <cstdio>
#include <array>
#include <cstdlib>
using std::array;
struct Widget
{
int val = 0;
Widget() { printf("default ctor\n"); }
Widget(const Widget&) { printf("copy ctor\n"); }
Widget(Widget&&) { printf("move ctor\n"); }
Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
Widget& operator=(Widget&&) { printf("move assign\n"); return *this; }
~Widget() { printf("dtor\n"); }
void method(int)
{
printf("method\n");
}
};
bool some_condition(int x)
{
return (x % 2) == 0;
}
array<int, 3> something()
{
return {{1,2,3}};
}
Widget build_vector(int n)
{
if (some_condition(n)) return {};
Widget out;
for(int x : something())
{
out.method(x);
}
return out;
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
return -1;
}
const int x = atoi(argv[1]);
printf("call build_vector\n");
Widget w = build_vector(x);
printf("end of call\n");
return w.val;
}
V1的输出
call build_vector
default ctor
method
method
method
move ctor
dtor
end of call
dtor
V2使用nrvo_friendly_build_vector()
:http://coliru.stacked-crooked.com/a/51b036c66e993d62
#include <cstdio>
#include <array>
#include <cstdlib>
using std::array;
struct Widget
{
int val = 0;
Widget() { printf("default ctor\n"); }
Widget(const Widget&) { printf("copy ctor\n"); }
Widget(Widget&&) { printf("move ctor\n"); }
Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
Widget& operator=(Widget&&) { printf("move assign\n"); return *this; }
~Widget() { printf("dtor\n"); }
void method(int)
{
printf("method\n");
}
};
bool some_condition(int x)
{
return (x % 2) == 0;
}
array<int, 3> something()
{
return {{1,2,3}};
}
Widget nrvo_friendly_build_vector(int n)
{
Widget out;
if (some_condition(n)) return out;
for(int x : something())
{
out.method(x);
}
return out;
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
return -1;
}
const int x = atoi(argv[1]);
printf("call nrvo_friendly_build_vector\n");
Widget w = nrvo_friendly_build_vector(x);
printf("end of call\n");
return w.val;
}
V2的输出
call nrvo_friendly_build_vector
default ctor
method
method
method
end of call
dtor
如您所见,在这种特殊情况下(某种条件下看不到构造该结构的副作用),如果some_condition()
为假,则V1调用move构造函数(至少在clang和gcc中使用Coliru中的-std=c++11
和-O2
)
此外,与you have noticed一样,-O3
也会发生相同的行为。
HTH
ps:在学习复制省略时,您可能会发现Abseil's ToW #11有趣;)