Почему здесь не происходит RVO?

Я прочитал Дейва Абрахамса статья на RVO и несколько других Q / As на SO (14043609, 9293726 а также 10818278) но у меня еще есть вопрос. Когда я компилирую и запускаю следующий код, я получаю такой вывод:

Address of v in func    0x7fffac6df620
Address of v.data in func       0x2081010
Address of v in main    0x7fffac6df690
Address of v.data in func       0x20811b0
9

Мне кажется, что копия сделана. Как передать большие объекты из функций? Обратите внимание, что я хочу вернуть один или несколько объектов без написания явной структуры для него. Я использовал GCC 4.6.3 с -O2. Изменить: первые два ответа показали мне, что я ожидал слишком много от компилятора. Я добавил main2, который ведет себя так же, например, печатные адреса разные. Я хотел бы подчеркнуть, что мотивация — это эффективный возврат крупных объектов.

#include <iostream>
#include <vector>
#include <tuple>

std::tuple<std::vector<int>, double> func() {
std::vector<int> v;
v.reserve(100);
for (int k=0;k!=100;k+=1)
v.push_back(k);

double a = 5.0;
std::cout << "Address of v in func\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
return make_tuple(v, a);
}

int main() {
std::vector<int> v;
double a;
std::tie(v, a) = func();
std::cout << "Address of v in main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
std::cout << v[9] << std::endl;
return 0;
}int main2() {
auto tp = func();
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);
std::cout << "Address of v in main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
std::cout << v[9] << std::endl;
return 0;
}

3

Решение

Как уже было сказано, есть две вещи, которые мешают RVO. Функция не возвращает v, но вместо этого кортеж, который построен в форме v а также a, Также в основной функции v назначается и не создается из возвращаемого значения.

Чтобы получить то, что вы хотите, вы можете использовать кортежи напрямую без дополнительных векторных объектов:

#include <iostream>
#include <vector>
#include <tuple>

std::tuple<std::vector<int>, double> func() {
std::tuple<std::vector<int>, double> t;
get<0>(t).reserve(100);
for (int k=0;k!=100;k+=1)
get<0>(t).push_back(k);

get<1>(t) = 5.0;
std::cout << "Address of v in func\t" << &get<0>(t) << std::endl;
std::cout << "Address of v.data in func\t" << get<0>(t).data() << std::endl;
return t;
}

int main()
{
std::tuple<std::vector<int>, double> t = func();
std::cout << "Address of v in main\t" << &get<0>(t) << std::endl;
std::cout << "Address of v.data in func\t" << get<0>(t).data() << std::endl;
std::cout << get<0>(t)[9] << std::endl;

return 0;
}

Выход:

Address of v in func    0x28fe80
Address of v.data in func       0x962c08
Address of v in main    0x28fe80
Address of v.data in func       0x962c08
9

Альтернативная оптимизация заключается в использовании семантики перемещения при построении кортежа:

 return make_tuple(std::move(v), a);

В этом случае избегается, по крайней мере, копирование внутреннего буфера вектора:

Address of v in func    0x28fdd4
Address of v.data in func       0xa72c08
Address of v in main    0x28fe64
Address of v.data in func       0xa72c08
9
4

Другие решения

Поскольку оба v а также a были объявлены в качестве переменных в main(), нет копии для elide. То, что вы получаете здесь — это копирование, а не копирование Это эквивалент этого:

struct Foo {};

Foo foo() { return Foo(); }

int main()
{
Foo f1;
f1 = foo();  // no copy hence f1 is distinct from object returned
Foo f2 = foo(); // We can get RVO here, returned object can be f2.
}
3

RVO вполне может происходить здесь, но единственная возможность для исключения в коде, который вы дали, это копирование возвращаемого значения make_tuple(v, a) в возвращаемое значение func(),

Независимо от того, сделано это или нет, std::vector а также double все равно будет скопирован. Вы просто присваиваете результат func() в v а также a в main, Разрешение копирования (и RVO) применимо только к конструкции копирования / перемещения, но не к назначению.

Когда вы делаете &v в mainвы просто получаете адрес v объект, определенный в первой строке main, Конечно, это отличается от v объект, определенный в func,

2

В вашем первом примере данные копируются в присваивании:

int main() {
std::vector<int> v;
double a;
std::tie(v, a) = func();

Во втором примере данные все еще копируются при создании кортежа. Этот модифицированный пример показывает, что RVO действительно происходит:

#include <iostream>
#include <vector>
#include <tuple>

std::tuple<std::vector<int>, double> func() {
std::vector<int> v;
v.reserve(100);
for (int k=0;k!=100;k+=1)
v.push_back(k);

double a = 5.0;

const auto ret = make_tuple(v, a);
const auto &v1 = std::get<0>(ret);

std::cout << "Address of v in func\t" << &v1 << std::endl;
std::cout << "Address of v.data in func\t" << v1.data() << std::endl;

return ret;
}

int main() {
auto tp = func();
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);
std::cout << "Address of v in main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
std::cout << v[9] << std::endl;

(void)a;
}
2

Спасибо за ответ. Я нашел Тимо ответ самый полезный Вот как я адаптировал этот ответ к своему стилю. Обратите внимание на дублированный шаблон в обоих func а также main, Конечно, если кто-то знает, как от этого избавиться, было бы здорово!

#include <iostream>
#include <vector>
#include <tuple>

std::tuple<std::vector<int>, double> func() {
std::tuple<std::vector<int>, double> tp;
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);

v.reserve(100);
for (int k=0;k!=100;k+=1)
v.push_back(k);

a = 5.0;
std::cout << "Address of v in func\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
return tp;
}

int main() {
std::tuple<std::vector<int>, double> tp = func();
std::vector<int> & v = std::get<0>(tp);
double & a = std::get<1>(tp);

std::cout << "Address of v in main\t" << &v << std::endl;
std::cout << "Address of v.data in func\t" << v.data() << std::endl;
std::cout << v[9] << std::endl;

(void)a;
return 0;
}
0
По вопросам рекламы [email protected]