为什么我不能使用 std::tuple 保证复制省略?
Why do I not get guaranteed copy elision with std::tuple?
我希望在 C++20 中,以下代码在 A 和 B 的打印之间不打印任何内容(因为我希望保证 RVO 启动)。但输出是:
A
Bye
B
C
Bye
Bye
所以大概正在创建一个临时文件。
#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";
}
这种行为的原因是什么?
是否有避免复制的解决方法(不使用指针)?
从 {i, {47}}
构造 std::tuple<int, INeedElision>
时,the selected constructor of std::tuple
从 lvalue-reference 到 const
获取元素。
tuple( const Types&... args );
然后当使用 {i, {47}}
作为初始化器时,一个临时的 INeedElision
将被构造,然后传递给 std::tuple
的构造函数(并被复制)。临时对象将立即销毁,您将在“A”和“B”之间看到“再见”。
顺便说一句:std::tuple
的第三个构造函数不会用于这种情况。
template< class... UTypes >
tuple( UTypes&&... args );
它是一个构造器模板,braced-init-list和{47}
一样没有类型,不能通过模板参数推导来推导。
另一方面,如果 INeedElision
有一个采用 int
的转换构造函数,并将初始化器设为 {i, 47}
,则将使用 std::tuple
的第三个构造函数并且没有构造临时 INeedElision
;该元素将从 int
47
.
构造 in-place
如果你 return 对象本身,你只会得到复制省略:
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
}
在您的情况下,您正在 returning 元组,因此元组本身可能会被复制省略,但传递给元组构造函数的参数不会被省略!
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
}
不允许编译器省略传递给元组构造函数的参数副本,因为您不是return参数本身,而是包含参数副本的元组。另请注意,table 无法保存对参数的引用,因为这些局部变量将在函数 returns 导致悬空引用时被破坏。
如果你想在 C++ 17 中获得复制省略的机会,然后做这样的事情:
std::tuple<int, INeedElision> f(){
std::tuple<int, INeedElision> ret;
auto& [i, ne] = ret;
i = 47;
ne = 47;
return ret;
}
我希望在 C++20 中,以下代码在 A 和 B 的打印之间不打印任何内容(因为我希望保证 RVO 启动)。但输出是:
A
Bye
B
C
Bye
Bye
所以大概正在创建一个临时文件。
#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";
}
这种行为的原因是什么? 是否有避免复制的解决方法(不使用指针)?
从 {i, {47}}
构造 std::tuple<int, INeedElision>
时,the selected constructor of std::tuple
从 lvalue-reference 到 const
获取元素。
tuple( const Types&... args );
然后当使用 {i, {47}}
作为初始化器时,一个临时的 INeedElision
将被构造,然后传递给 std::tuple
的构造函数(并被复制)。临时对象将立即销毁,您将在“A”和“B”之间看到“再见”。
顺便说一句:std::tuple
的第三个构造函数不会用于这种情况。
template< class... UTypes > tuple( UTypes&&... args );
它是一个构造器模板,braced-init-list和{47}
一样没有类型,不能通过模板参数推导来推导。
另一方面,如果 INeedElision
有一个采用 int
的转换构造函数,并将初始化器设为 {i, 47}
,则将使用 std::tuple
的第三个构造函数并且没有构造临时 INeedElision
;该元素将从 int
47
.
如果你 return 对象本身,你只会得到复制省略:
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
}
在您的情况下,您正在 returning 元组,因此元组本身可能会被复制省略,但传递给元组构造函数的参数不会被省略!
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
}
不允许编译器省略传递给元组构造函数的参数副本,因为您不是return参数本身,而是包含参数副本的元组。另请注意,table 无法保存对参数的引用,因为这些局部变量将在函数 returns 导致悬空引用时被破坏。
如果你想在 C++ 17 中获得复制省略的机会,然后做这样的事情:
std::tuple<int, INeedElision> f(){
std::tuple<int, INeedElision> ret;
auto& [i, ne] = ret;
i = 47;
ne = 47;
return ret;
}