19

我正在研究元组/关系的返回值优化,我观察到的行为与我预期的不同。在下面的示例中,我希望移动语义能够发挥作用,它确实如此,但是仍然存在一个复制操作。以下优化后的输出为:

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;
}
4

4 回答 4

8

移动构造函数不会被自动调用,因为您正在调用

std::make_tuple(tmp, 99);

在这种情况下,tmp是一个左值。您可以使用std::move将其转换为右值引用:

return std::make_tuple(std::move(tmp), 99);

这将指示编译器使用移动构造函数。

于 2016-03-11T09:19:21.847 回答
5

副本出现在这里:

std::make_tuple(tmp, 99);

虽然您可以看到tmp可能可以直接在元组中构造,但tmp不会省略从到元组的副本。您真正想要的是一种传递参数的方法,用于std::tuple构造其内部Aint对象。没有这样的东西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();

从输出中可以看出,您的副本已经消失。它不会被其他答案中的举动所取代,而是完全删除了:

测试二重奏输出,非参考元组

调用的默认构造函数

调用的参数构造函数

调用了移动赋值运算符

100

Live Demo

于 2016-03-11T09:24:53.160 回答
3

副本发生在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);
}
于 2016-03-11T09:12:24.360 回答
1

你不动tmp

 A tmp(100);

 return std::make_tuple(std::move(tmp), 99);
于 2016-03-11T09:11:29.207 回答