Правильный способ (семантика перемещения) для возврата std::vector из вызова функции в С++ 11

Я хочу заполнить std::vector (или другой контейнер STL):

class Foo {
public:
  Foo(int _n, const Bar &_m);
private:
  std::vector<Foo> fooes_;
}

1.Красивый актер, высокая производительность

std::vector<Foo> get_vector(int _n, const Bar &_m) {
  std::vector<Foo> ret;
  ... // filling ret depending from arguments
  return ret;
}

Foo::Foo(int _n, const Bar &_m) : fooes_(get_vector(_n, _m) {}

2. Лучшая производительность, худший внешний вид ctor

void fill_vector(int _n, const Bar &_m, std::vector<Foo> &_ret) {
  ... // filling ret depending from arguments
}

Foo::Foo(int _n, const Bar &_m) { fill_vector(_n, _m, fooes_); }

Можно ли переписать функцию get_vector из 1-го примера с C++0x (переместить функции семантики и т. д.), чтобы избежать избыточного копирования и вызовов конструктора?


person Loom    schedule 02.06.2011    source источник
comment
Можете ли вы уточнить, будет ли ваш параметр _m изменяться этими функциями? И что делается с _m внутри этих функций? Он скопирован в get_vector?   -  person Johannes Schaub - litb    schedule 02.06.2011
comment
@Johannes - Спасибо за удаленный ответ (+1). Я добавил 2 разных аргумента только для иллюстрации. Я не слишком интересуюсь их природой. Вы правы, лучше добавить константу, чтобы не вызывать путаницы. Я не совсем понимаю ссылки rvalue. Должен ли Bar иметь перемещение ctor в Foo::Foo(int _n, Bar _m) fooes_(get_vector(_n, move(_m)) {}?   -  person Loom    schedule 02.06.2011
comment
Вместо того, чтобы описывать здесь все тонкости РВО, переезда и т.д. вот ссылка на статью, где подробно рассматривается это: cpp-next.com/archive/2009/08/want-speed-pass-by-value   -  person Gene Bushuyev    schedule 03.06.2011


Ответы (3)


Если вы используете C++0x-совместимый компилятор и стандартную библиотеку, вы получите более высокую производительность из первого примера без каких-либо действий. Возвращаемое значение get_vector(_n, _m) является временным, и конструктор перемещения для std::vector (конструктор, использующий ссылку rvalue) будет автоматически вызываться без каких-либо дополнительных действий с вашей стороны.

Как правило, авторам, не работающим с библиотеками, не нужно напрямую использовать ссылки rvalue; вы просто автоматически пожинаете приличную часть преимуществ.

person John Calsbeek    schedule 02.06.2011

Я считаю, что (1) и (2) имеют одинаковую производительность даже без C++0x, если ваш компилятор выполняет оптимизацию именованного возвращаемого значения, что, как я полагаю, делает большинство. Не следует делать ни копий, ни ни перемещений.

Пожалуйста, поправьте меня, если я ошибаюсь, потому что если это так, я неправильно понимаю NRVO.

person Clinton    schedule 02.06.2011
comment
Это правильно. И даже в C++0x компилятор сначала считает RVO, и если не может, то считает движущийся объект, если это не разрешено, то делает копирование в крайнем случае. - person Gene Bushuyev; 03.06.2011

В конкретном случае, который вы рассматриваете, первая реализация столь же эффективна, как и вторая. Компилятор оптимизирует копию ret в функции get_vector для возвращаемого значения и будет использовать семантику перемещения для передачи владения вектором классу-контейнеру. Для построения перемещения в векторе требуется (зависит от реализации, но в хорошем приближении) 3 копии указателя, независимо от количества и размеров элементов в контейнере. Для передачи вектора в качестве ссылки для изменения требуется одна копия указателя (опять же приблизительная стоимость), но любая операция, которую вы выполняете с вектором, будет доминировать в стоимости любого варианта.

Есть некоторые очень специфические обстоятельства, когда передача вектора в функцию для модификации может быть быстрее, но они редки и больше связаны с предметной областью, чем с самим вектором. Просто игнорируйте это, сначала кодируйте для удобства сопровождения, а если программа медленная, профилируйте, определяйте, где стоимость программы, и только потом думайте об оптимизации. Интересно то, что после того, как вы профилировали, вы, вероятно, знаете, в чем узкое место, и это означает, что у вас будут подсказки, что нужно изменить.

person David Rodríguez - dribeas    schedule 02.06.2011
comment
Что касается производительности, это зависит: если вы пишете что-то вроде std::vector<Foo> v(get_vector(...)), компилятор должен иметь возможность оптимизировать копию. Если вы делаете v = get_vector(...) на существующем std::vector<Foo>, вероятно, нет. - person James Kanze; 02.06.2011
comment
@James Kanze: это основная причина комментария некоторые особые обстоятельства при передаче вектора могут быть быстрее. Выполнение clear() в контейнере не освободит память, а если новые элементы поместятся в тот же контейнер, то захват памяти тоже не потребуется, так будет эффективнее. Но говоря о создании объекта (как в случае инициализации члена в вопросе), они будут эквивалентны. - person David Rodríguez - dribeas; 02.06.2011
comment
@ Дэвид Родригес Или нет. Единственный способ убедиться в этом — попробовать и то, и другое и измерить (и не стоит заморачиваться, пока действительно не возникнет проблема с производительностью). В одном или двух особых случаях я обнаружил, что быстрее перемещать переменную за пределы цикла по причине, которую вы упомянули: после того, как память будет выделена, она не будет освобождена, поэтому вы получите меньше перераспределений. (Но это не общее правило.) Мой совет — просто вернуть контейнер, пока это не станет проблемой производительности, а затем поэкспериментировать с различными решениями, чтобы найти то, которое решит проблему. - person James Kanze; 02.06.2011
comment
@James Если вы сделаете v = get_vector(...) на существующем std::vector<Foo>, копии не будет; оператор присваивания перемещения этого вызова vector<>, поскольку get_vector возвращает значение rvalue. Я не понимаю, к чему ты клонишь... - person ildjarn; 02.06.2011
comment
@ildjarn Не с моими компиляторами, это не так. Семантика перемещения недоступна в большинстве компиляторов, а те, которые использую я, не используют их в стандартной библиотеке, даже если они доступны. - person James Kanze; 02.06.2011
comment
@James: Это происходит с моим компилятором (VC++ 2010), и FDIS предписывает, что так и должно быть; это достаточно хорошо для меня. - person ildjarn; 02.06.2011
comment
@idljarn: я должен согласиться с Джеймсом здесь: FDIS даже не является утвержденным стандартом, и не каждый может использовать последний компилятор, доступный для их платформы (черт возьми, я даже не использую Windows!) - person David Rodríguez - dribeas; 03.06.2011
comment
@David: Вопрос касается семантики перемещения в C++0x — вы говорите, что для вопроса с тегом c++0x мы не должны рассматривать FDIS C++0x как авторитетный? В этом нет никакого смысла, особенно если учесть, что в этом случае есть несколько компиляторов доставки, которые придерживаются FDIS в этом отношении. С другой стороны, применение правил С++ 03 к вопросу с пометкой и тегом С++ 0x довольно бесполезно, и, похоже, это то, за что вы выступаете. Помогите, это шутка? - person ildjarn; 03.06.2011
comment
@ildjarn: :) Я пропустил тег. Виноват - person David Rodríguez - dribeas; 03.06.2011