问题描述
我希望在C ++ 20中,以下代码在A和B的打印之间不会打印任何内容(因为我希望可以保证RVO起作用)。但是输出是:
A
再见
B
C
再见
再见
因此大概正在创建一个临时文件。
#include <iostream>
#include <tuple>
struct INeedElision{
int i;
~INeedElision(){
std::cout << "Bye\n";
}
};
std::tuple<int,INeedElision> f(){
int i = 47;
return {i,{47}};
}
INeedElision g(){
return {};
}
int main()
{
std::cout << "A\n";
auto x = f();
std::cout << "B\n";
auto y = g();
std::cout << "C\n";
}
此行为的原因是什么? 有没有避免复制的解决方法(不使用指针)?
解决方法
从std::tuple<int,INeedElision>
构造{i,{47}}
时,the selected constructor of std::tuple
通过对const
的左值引用来获取元素。
tuple( const Types&... args );
然后,在使用{i,{47}}
作为初始化程序时,将构造一个临时INeedElision
,然后将其传递给std::tuple
的构造函数(并进行复制)。临时对象将被立即销毁,并且您会在“ A”和“ B”之间看到“再见”。
顺便说一句:std::tuple
的第3个构造函数在这种情况下将不会使用。
template< class... UTypes > tuple( UTypes&&... args );
这是一个构造函数模板,并且{47}
之类的括号初始列表没有类型,也无法通过模板参数推导来推导。
另一方面,如果INeedElision
的转换构造函数使用int
,并将初始化程序设为{i,47}
,则将使用std::tuple
的第三个构造函数,并且不使用构造临时INeedElision
;元素将从int
47
就地构建。
仅当您返回对象本身时,才会得到复制省略:
std::vector<int> fn1()
{
return std::vector<int>{}; // guaranteed copy elision
}
std::vector<int> fn2()
{
std::vector<int> vec;
return vec; // a good compiler will manage to elide the copy/move here
}
在您的情况下,您将返回元组,因此元组本身可能会被复制,但不会传递给元组的构造函数的参数!
std::tuple<int,INeedElision> f(){
int i = 47;
return {i,{47}}; // construct the tuple in place of the return address but the arguments are copied into the tuple and not even moved ! to move call std::move explicitly
}
不允许编译器取消传递给元组构造函数的参数副本,因为您不是返回参数本身,而是返回包含参数副本的元组。还要注意,表不能保存对参数的引用,因为这些局部变量在函数返回时将被破坏,从而导致悬挂的引用。
如果您想在c ++ 17中获得复制省略的机会,然后再执行以下操作:
std::tuple<int,INeedElision> f(){
std::tuple<int,INeedElision> ret;
auto& [i,ne] = ret;
i = 47;
ne = 47;
return ret;
}