Возврат вектора STL из функции - стоимость копирования - программирование
Подтвердить что ты не робот

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

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

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

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

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

Ответ 1

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

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

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

  • Используйте вызов функции как инициализатор для vector<int>, и в этом случае снова любой надежный компилятор С++ будет удалять копию.
  • Используйте С++ 11, где vector имеет семантику перемещения.
  • В С++ 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(). Это не нужно в С++ 11, где вместо дешевого назначения копирования есть дешевое назначение переноса, но, конечно, оно все еще работает.

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

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

[*], конечно, правило "как будто" все еще применяется, и можно представить себе реализацию С++, которая достаточно умна, чтобы понять, что, поскольку это вектор тривиально-скопируемого типа (int), а так как вы haven "Я принимал какие-либо указатели на какие-либо элементы (я полагаю), тогда он может заменить swap, а результат" как будто "он скопировал. Но я не стал бы рассчитывать на это.

Ответ 2

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

Вот небольшой пример с подробным фиктивным классом, скомпилированным без поддержки оптимизации или С++ 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();
}

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