Возврат вектора STL из функции — стоимость копирования

Когда вы возвращаете вектор stl из функции:

vector<int> getLargeArray() {  ...  }

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

void getLargeArray( vector<int>& vec ) {  ...  }

17

Решение

Предполагая, что ваша функция создает и возвращает новые данные, вы должны вернуться по значению и попытаться убедиться, что у самой функции есть одна точка возврата, которая возвращает переменную типа vector<int>или, в худшем случае, несколько точек возврата, которые все возвращают одну и ту же переменную.

Это гарантирует, что вы получите оптимизацию именованного возвращаемого значения на любом надежном компиляторе, что исключает один потенциальных копий (от значения в функции до возвращаемого значения). Существуют и другие способы оптимизации возвращаемого значения, но это не совсем предсказуемо, поэтому простое правило играет безопасно.

Затем вы хотите исключить потенциальную копию из возвращаемого значения во все, что делает с ним вызывающая сторона. Это проблема вызывающего абонента, а не вызываемого абонента, и есть три основных способа сделать это:

  • Используйте вызов функции в качестве инициатора для vector<int>, в этом случае снова любой заслуживающий доверия компилятор C ++ исключит копию.
  • Используйте C ++ 11, где vector имеет ход семантики.
  • В C ++ 03 используйте «swaptimization».

В С ++ 03 не записывать

vector<int> v;
// use v for some stuff
// ...
// now I want fresh data in v:
v = getLargeArray();

Вместо:

getLargeArray().swap(v);

Это позволяет избежать назначения копирования, которое требуется (не должно быть исключено [*]) для v = getLargeArray(), Это не требуется в C ++ 11, где вместо дорогостоящего копирования назначается дешевое назначение перемещения, но, конечно, оно все еще работает.

Еще одна вещь, чтобы рассмотреть, действительно ли вы хотите vector как часть вашего интерфейса. Вместо этого можно было бы написать шаблон функции, который принимает выходной итератор и записывает данные в этот выходной итератор. Вызывающие абоненты, которым нужны данные в векторе, могут затем передать результат std::back_inserterи так могут абоненты, которые хотят данные в deque или же list, Вызывающие абоненты, заранее знающие размер данных, могут даже передать только векторный итератор (соответственно resize()d) или необработанный указатель на достаточно большой массив, чтобы избежать back_insert_iterator, Существуют не шаблонные способы сделать то же самое, но они, скорее всего, повлекут за собой накладные расходы на вызовы, так или иначе. Если вы беспокоитесь о стоимости копирования int за элемент, то вы беспокоитесь о стоимости вызова функции для элемента.

Если ваша функция не создает и не возвращает новые данные, а возвращает текущее содержимое некоторых существующих vector<int> и не разрешается изменять оригинал, тогда вы не сможете избежать хотя бы одной копии, когда вернетесь по значению. Таким образом, если производительность является проверенной проблемой, то вам нужно взглянуть на какой-нибудь API, кроме возврата по значению. Например, вы можете предоставить пару итераторов, которые можно использовать для обхода внутренних данных, функцию для поиска значения в векторе по индексу или даже (если проблема с производительностью настолько серьезна, что может потребовать раскрытия ваших внутренних данных), ссылка на вектор. Очевидно, что во всех этих случаях вы меняете значение функции — теперь вместо того, чтобы давать вызывающей стороне «свои данные», она предоставляет представление о чужих данных, которые могут измениться.

[*] конечно, правило «как будто» все еще применяется, и можно представить реализацию C ++, которая достаточно умна, чтобы понять, что, поскольку это вектор тривиально копируемого типа (int), и поскольку вы не взяли никаких указателей на какие-либо элементы (я полагаю), тогда он может поменяться местами, и в результате «как будто» он скопируется. Но я бы на это не рассчитывал.

23

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

Вы, скорее всего, получите оптимизация возвращаемого значения, в зависимости от структуры тела функции. В C ++ 11 вы также можете извлечь выгоду из семантики перемещения.
Возврат по значению, безусловно, имеет более чистую семантику, и я считаю его предпочтительным вариантом, если только профилирование не окажется дорогостоящим. Есть хорошая связанная статья Вот.

Вот небольшой пример с подробным фиктивным классом, скомпилированным без оптимизации или поддержки C ++ 11, с использованием старой версии GCC (4.3.4):

#include <vector>
#include <iostream>
struct Foo
{
Foo() { std::cout << "Foo()\n"; }
Foo(const Foo&) { std::cout << "Foo copy\n"; }
Foo& operator=(const Foo&) {
std::cout << "Foo assignment\n";
return *this;
}
};

std::vector<Foo> makeFoos()
{
std::vector<Foo> tmp;
tmp.push_back(Foo());
std::cout << "returning\n";
return tmp;
}

int main()
{
std::vector<Foo> foos = makeFoos();
}

В результате на моей платформе все копирование происходит до возврата функции. Если я скомпилирую с поддержкой C ++ 11, то push_back даст в результате копию перемещения, а не конструкцию копии C ++ 03.

10

По вопросам рекламы [email protected]