Return tuple/tie 的价值优化
Return value optimization of tuple/tie
我正在研究 tuple/ties 情况下的 return 值优化,我观察到的行为与我预期的不一样。在下面的示例中,我希望移动语义能够启动,确实如此,但是仍然存在一个复制操作。下面优化后的输出是:
Test duo output, non_reference tuple
Default constructor invoked
Parameter constructor invoked
Copy constructor invoked
Move Assignment operator invoked
100
在函数内部创建元组时调用复制构造函数似乎是不必要的。有什么方法可以删除它吗?我正在使用 MSVC 2012 编译器。
#include <iostream>
#include <tuple>
class A
{
public:
int value;
A() : value(-1)
{
std::cout << "Default constructor invoked" << std::endl;
}
explicit A(const int v) : value(v)
{
std::cout << "Parameter constructor invoked" << std::endl;
}
A(const A& rhs)
{
value = rhs.value;
std::cout << "Copy constructor invoked" << std::endl;
}
A(const A&& rhs)
{
value = rhs.value;
std::cout << "Move constructor invoked" << std::endl;
}
A& operator=(const A& rhs)
{
value = rhs.value;
std::cout << "Assignment operator invoked" << std::endl;
return *this;
}
A& operator=(const A&& rhs)
{
value = rhs.value;
std::cout << "Move Assignment operator invoked" << std::endl;
return *this;
}
};
std::tuple<A, int> return_two_non_reference_tuple()
{
A tmp(100);
return std::make_tuple(tmp, 99);
}
int main(int argc, char* argv[])
{
std::cout << "Test duo output, non_reference tuple" << std::endl;
A t3;
int v1;
std::tie(t3, v1) = return_two_non_reference_tuple();
std::cout << t3.value << std::endl << std::endl;
system("pause");
return 0;
}
你不动tmp
:
A tmp(100);
return std::make_tuple(std::move(tmp), 99);
复制发生在 return_two_non_reference_tuple()
。
删除它的一种方法是移动 tmp
std::tuple<A, int> return_two_non_reference_tuple()
{
A tmp(100);
return std::make_tuple(std::move(tmp), 99);
}
或其他方式
std::tuple<A, int> return_two_non_reference_tuple()
{
return std::make_tuple(A(100), 99);
}
不会自动调用移动构造函数,因为您正在调用
std::make_tuple(tmp, 99);
在这种情况下,tmp
是一个左值。您可以使用 std::move
将其转换为右值引用:
return std::make_tuple(std::move(tmp), 99);
这将指示编译器使用移动构造函数。
复制发生在这里:
std::make_tuple(tmp, 99);
虽然可以看到tmp
可能可以直接在元组中构造,但是从tmp
到元组的拷贝不会被省略。您真正想要的是一种为 std::tuple
传递参数以用于构造其内部 A
和 int
对象的方法。 std::tuple
没有这样的东西,但是有一种方法可以达到同样的效果。
因为你只有两种类型,你可以使用 std::pair
。这有一个 std::piecewise_construct
构造函数,它需要两个 std::tuples
包含要传递给内部对象的构造函数的参数。
std::pair<A, int> return_two_non_reference_tuple()
{
return {std::piecewise_construct,
std::make_tuple(100), std::make_tuple(99)};
}
此解决方案的妙处在于,您仍然可以在调用站点使用 std::tie
,因为 std::tuple
具有来自 std::pair
的赋值运算符。
std::tie(t3, v1) = return_two_non_reference_tuple();
正如您从输出中看到的那样,您的副本不见了。它没有像其他答案中那样被移动取代,它被完全删除:
Test duo output, non_reference tuple
Default constructor invoked
Parameter constructor invoked
Move Assignment operator invoked
100
我正在研究 tuple/ties 情况下的 return 值优化,我观察到的行为与我预期的不一样。在下面的示例中,我希望移动语义能够启动,确实如此,但是仍然存在一个复制操作。下面优化后的输出是:
Test duo output, non_reference tuple
Default constructor invoked
Parameter constructor invoked
Copy constructor invoked
Move Assignment operator invoked
100
在函数内部创建元组时调用复制构造函数似乎是不必要的。有什么方法可以删除它吗?我正在使用 MSVC 2012 编译器。
#include <iostream>
#include <tuple>
class A
{
public:
int value;
A() : value(-1)
{
std::cout << "Default constructor invoked" << std::endl;
}
explicit A(const int v) : value(v)
{
std::cout << "Parameter constructor invoked" << std::endl;
}
A(const A& rhs)
{
value = rhs.value;
std::cout << "Copy constructor invoked" << std::endl;
}
A(const A&& rhs)
{
value = rhs.value;
std::cout << "Move constructor invoked" << std::endl;
}
A& operator=(const A& rhs)
{
value = rhs.value;
std::cout << "Assignment operator invoked" << std::endl;
return *this;
}
A& operator=(const A&& rhs)
{
value = rhs.value;
std::cout << "Move Assignment operator invoked" << std::endl;
return *this;
}
};
std::tuple<A, int> return_two_non_reference_tuple()
{
A tmp(100);
return std::make_tuple(tmp, 99);
}
int main(int argc, char* argv[])
{
std::cout << "Test duo output, non_reference tuple" << std::endl;
A t3;
int v1;
std::tie(t3, v1) = return_two_non_reference_tuple();
std::cout << t3.value << std::endl << std::endl;
system("pause");
return 0;
}
你不动tmp
:
A tmp(100);
return std::make_tuple(std::move(tmp), 99);
复制发生在 return_two_non_reference_tuple()
。
删除它的一种方法是移动 tmp
std::tuple<A, int> return_two_non_reference_tuple()
{
A tmp(100);
return std::make_tuple(std::move(tmp), 99);
}
或其他方式
std::tuple<A, int> return_two_non_reference_tuple()
{
return std::make_tuple(A(100), 99);
}
不会自动调用移动构造函数,因为您正在调用
std::make_tuple(tmp, 99);
在这种情况下,tmp
是一个左值。您可以使用 std::move
将其转换为右值引用:
return std::make_tuple(std::move(tmp), 99);
这将指示编译器使用移动构造函数。
复制发生在这里:
std::make_tuple(tmp, 99);
虽然可以看到tmp
可能可以直接在元组中构造,但是从tmp
到元组的拷贝不会被省略。您真正想要的是一种为 std::tuple
传递参数以用于构造其内部 A
和 int
对象的方法。 std::tuple
没有这样的东西,但是有一种方法可以达到同样的效果。
因为你只有两种类型,你可以使用 std::pair
。这有一个 std::piecewise_construct
构造函数,它需要两个 std::tuples
包含要传递给内部对象的构造函数的参数。
std::pair<A, int> return_two_non_reference_tuple()
{
return {std::piecewise_construct,
std::make_tuple(100), std::make_tuple(99)};
}
此解决方案的妙处在于,您仍然可以在调用站点使用 std::tie
,因为 std::tuple
具有来自 std::pair
的赋值运算符。
std::tie(t3, v1) = return_two_non_reference_tuple();
正如您从输出中看到的那样,您的副本不见了。它没有像其他答案中那样被移动取代,它被完全删除:
Test duo output, non_reference tuple
Default constructor invoked
Parameter constructor invoked
Move Assignment operator invoked
100