英文:
NRVO. Turning off elision. C++11 vs C++17
问题
我正在研究复制省略(copy elision)以及返回值优化(RVO/NRVO)。当我使用 g++ 编译时,使用 -fno-elide-constructors
标志时,我发现对于 C++11 和 C++17 会有不同的行为。
我了解到,C++17 在特定情况下要求进行 RVO。我本以为下面的代码符合 NRVO(除非编译器首先也消除了命名变量 y
?)
struct rvo
{
rvo () : val {0} { std::cout << "Default Constructor" << std::endl;};
~rvo (){ std::cout << "Destructor" << std::endl; };
rvo(const rvo& in){ std::cout << "Copy Constructor" << std::endl; }
rvo(rvo&& in){ std::cout << "Move Constructor" << std::endl; }
double val;
};
rvo f1(rvo& x){
rvo y {x};
y.val++;
return y;
}
int main()
{
rvo A{};
rvo B {f1(A)};
return 0;
}
没有额外标志编译时的输出如下:
Default Constructor
Copy Constructor
Destructor
Destructor
当使用 -fno-elide-constructors
和 --std=c++11
编译时,输出如下:
Default Constructor
Copy Constructor
Move Constructor
Destructor
Move Constructor
Destructor
Destructor
Destructor
这是可以理解的,因为临时变量被创建。
我的问题是,当我使用 -fno-elide-constructors
和 --std=c++17
编译时,它消除了一个移动操作,输出如下:
Default Constructor
Copy Constructor
Move Constructor
Destructor
Destructor
Destructor
看起来它消除了临时返回变量,并将从函数变量 y
移回到 B
主函数中。即使我已关闭复制省略。它是将其视为强制执行的 RVO,还是我对某些基本概念有误解?
我找到了这个示例,https://stackoverflow.com/questions/61743861/rvo-nrvo-can-not-be-disabled-in-c17-in-clang,但那个示例显然是 RVO,而我认为我的示例是 NRVO,因此不是强制执行的。
如果有人可以确认,非常感谢。
英文:
I am looking at copy elision and RVO/NRVO. When I run g++ with -fno-elide-constructors
I see different behaviour for c++11 and c++17.
I understand that c++17 mandates RVO under certain circumstances. I would have assumed that the below counts as NRVO (unless the compiler first eliminates the named variable y
too?)
struct rvo
{
rvo () : val {0} { std::cout << "Default Constructor" << std::endl;};
~rvo (){ std::cout << "Destructor" << std::endl; };
rvo(const rvo& in){ std::cout << "Copy Constructor" << std::endl; }
rvo(rvo&& in){ std::cout << "Move Constructor" << std::endl; }
double val;
};
rvo f1(rvo& x){
rvo y {x};
y.val++;
return y;
}
int main()
{
rvo A{};
rvo B {f1(A)};
return 0;
}
Output when compiled up with no additional flags is as expected
Default Constructor
Copy Constructor
Destructor
Destructor
When compiled up with -fno-elide-constructors
and --std=c++11
the output is
Default Constructor
Copy Constructor
Move Constructor
Destructor
Move Constructor
Destructor
Destructor
Destructor
And this is fine as the temporary is being created.
My question is that when I compile up with -fno-elide-constructors
and --std=c++17
, it eliminates one of the moves.
Default Constructor
Copy Constructor
Move Constructor
Destructor
Destructor
Destructor
It appears that it is eliminating the temporary return variable, and doing a move from the function variable y
back to B
in main. Even though I have switched off elision. Is it considering it as mandatory RVO or am I misunderstanding something basic?
I found this example, https://stackoverflow.com/questions/61743861/rvo-nrvo-can-not-be-disabled-in-c17-in-clang but that one is obvious RVO whereas I thought my example was NRVO and thus not mandatory.
Thanks very much if anyone can confirm.
答案1
得分: 2
这是因为从[tag:C++17]开始有强制复制省略,而是直接从返回值f(A)
创建对象B
,因此没有使用移动构造函数或复制构造函数,因此输出如下。
这可以从复制省略文档中看出:
> 在以下情况下,编译器需要省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有可观察的副作用。对象直接构造在它们原本应该被复制/移动到的存储中。 复制/移动构造函数不一定要存在或可访问:
>
> * 在对象初始化时,当初始化表达式是相同类类型的纯右值(忽略cv资格限制)时:
> c++ > T x = T(T(f())); // 只调用一次T的默认构造函数,以初始化x >
英文:
This is because from [tag:C++17] there is mandatory copy elision and instead the object B
is created directly from the return value f(A)
so that there is no use of a move constructor or a copy ctor and hence the output.
This can be seen from copy elision documentation:
> Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
>
> * In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
>
> T x = T(T(f())); // only one call to default constructor of T, to initialize x
>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论