Оптимизация возвращаемого значения кортежа/привязки

Я рассматриваю оптимизацию возвращаемых значений в случае кортежей/связей, и поведение, которое я наблюдаю, не так, как я ожидал. В приведенном ниже примере я бы ожидал, что семантика перемещения будет задействована, и это произойдет, но остается одна операция копирования. Результат оптимизированного ниже:

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

Ответы

Ответ 1

Конструктор перемещения не будет вызываться автоматически, потому что вы вызываете

std::make_tuple(tmp, 99);

В этом случае tmp является значением l. Вы можете использовать std::move, чтобы передать его в ссылку rvalue:

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

Это даст указание компилятору использовать конструктор перемещения.

Ответ 2

Копия выполняется в 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);
}

Ответ 3

Копия происходит здесь:

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();

Как вы можете видеть на выходе, ваша копия исчезла. Он не заменяется ходом, как в других ответах, он полностью удален:

Вывод тестового дуэта, кортеж non_reference

Вызывается конструктор по умолчанию

Вызывается конструктор параметров

Оператор перемещения заданий, вызываемый

100

Live Demo

Ответ 4

Вы не двигаетесь tmp:

 A tmp(100);

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