【问题标题】:Return value optimization of tuple/tietuple/tie的返回值优化
【发布时间】:2016-06-26 10:42:12
【问题描述】:

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

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

【问题讨论】:

    标签: c++ c++11 stdtuple rvo


    【解决方案1】:

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

    std::make_tuple(tmp, 99);
    

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

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

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

    【讨论】:

      【解决方案2】:

      副本发生在这里:

      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

      【讨论】:

      • 谢谢,对于简单的情况,它是一个很好的替代方案,但是如果在 tmp 上执行更多操作,并且输出更多,它将无法正常工作。而且我认为它不会在 MSVC 2012 中起作用。
      • 这里发生了复制省略。问题是tmp 没有被移动到tuple。复制构造函数是将tmp复制到元组中,而不是被复制的元组。
      • @Simple 我知道,可能我的回答不够清楚。
      • @thorsan 如果对tmp 有更多操作要做,你可以在pair 的元素上执行它们,然后通过NRVO 省略pair 的返回。
      • @TartanLlama 没有。您不能依赖 (N)RVO,因为这只是编译器可以使用但不是必需的可选优化。事实上。 MSVC 17 (visual studio 15.3) 不支持调试模式下的 NRVO(仅 RVO),但它在发布模式下支持两者。 (我刚刚测试过)。但是,c++17 引入了保证复制省略。你可以依赖它(如果你的编译器还支持它)。它可以避免在不需要时一起构建(具体化)临时对象。
      【解决方案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);
      }
      

      【讨论】:

        【解决方案4】:

        你不动tmp:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-06-17
          相关资源
          最近更新 更多