Оптимизация возвращаемого значения кортежа/привязки
Я рассматриваю оптимизацию возвращаемых значений в случае кортежей/связей, и поведение, которое я наблюдаю, не так, как я ожидал. В приведенном ниже примере я бы ожидал, что семантика перемещения будет задействована, и это произойдет, но остается одна операция копирования. Результат оптимизированного ниже:
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);