В C++ это - все еще плохая практика для возврата вектора из функции?

Короткая версия: распространено возвратить большие объекты — такие как векторы/массивы — на многих языках программирования. Этот стиль теперь приемлем в C++ 0x, если класс имеет конструктора перемещения, или программисты на C++ считают это странным/ужасным/отвращением?

Долгая версия: В C++ 0x это все еще считается невоспитанностью?

std::vector<std::string> BuildLargeVector();
...
std::vector<std::string> v = BuildLargeVector();

Традиционная версия была бы похожа на это:

void BuildLargeVector(std::vector<std::string>& result);
...
std::vector<std::string> v;
BuildLargeVector(v);

В более новой версии, значение, возвращенное от BuildLargeVector rvalue, таким образом, v был бы создан с помощью конструктора перемещения std::vector, принятие (N) RVO не происходит.

Даже до C++ 0x первая форма часто было бы "эффективно" из-за (N) RVO. Однако (N) RVO на усмотрение компилятора. Теперь, когда у нас есть rvalue ссылки, гарантируется, что никакая глубокая копия не произойдет.

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

102
задан James McNellis 12 October 2010 в 00:54
поделиться

6 ответов

Дэйв Абрахамс провел довольно всесторонний анализ скорости передачи / возврата значений .

Краткий ответ: если вам нужно вернуть значение, верните значение. Не используйте выходные ссылки, потому что компилятор все равно это делает. Конечно, есть предостережения, поэтому вам следует прочитать эту статью.

73
ответ дан 24 November 2019 в 04:34
поделиться

Я все еще считаю это плохой практикой, но стоит отметить, что моя команда использует MSVC 2008 и GCC 4.1, поэтому мы не используем последние компиляторы.

Раньше многие горячие точки, показываемые в vtune с MSVC 2008, сводились к копированию строк. У нас был такой код:

String Something::id() const
{
    return valid() ? m_id: "";
}

... обратите внимание, что мы использовали наш собственный тип String (это было необходимо, потому что мы предоставляем комплект для разработки программного обеспечения, в котором авторы плагинов могут использовать разные компиляторы и, следовательно, разные несовместимые реализации std: : строка / std :: wstring).

Я внес простое изменение в ответ на сеанс профилирования выборки графа вызовов, показывающий, что String :: String (const String &) занимает значительное количество времени. Методы, подобные в приведенном выше примере, внесли наибольший вклад (на самом деле сеанс профилирования показал, что выделение и освобождение памяти является одной из самых больших горячих точек, причем конструктор копирования String является основным участником выделения).

Изменение, которое я сделал, было простым:

static String null_string;
const String& Something::id() const
{
    return valid() ? m_id: null_string;
}

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

Заключение: мы не используем самые последние компиляторы, но мы все еще не можем полагаться на компилятор, оптимизирующий копирование для надежного возврата по значению (по крайней мере, не во всех случаях). Это может быть не так для тех, кто использует более новые компиляторы, такие как MSVC 2010. Я с нетерпением жду, когда мы сможем использовать C ++ 0x и просто использовать ссылки rvalue, и никогда не придется беспокоиться о том, что мы пессимизируем наш код, возвращая сложный классы по стоимости.

[Изменить] Как указал Нейт, RVO применяется к возврату временных файлов, созданных внутри функции. В моем случае таких временных файлов не было (за исключением недопустимой ветки, в которой мы строим пустую строку), и поэтому RVO не был бы применим.

6
ответ дан 24 November 2019 в 04:34
поделиться

Немного придираемся: во многих языках программирования не принято возвращать массивы из функций. В большинстве из них возвращается ссылка на массив. В C ++ ближайшей аналогией будет возврат boost :: shared_array

3
ответ дан 24 November 2019 в 04:34
поделиться

Если производительность является реальной проблемой, вы должны понимать, что семантика перемещения не всегда быстрее, чем копирование. Например, если у вас есть строка, в которой используется оптимизация малых строк , то для небольших строк конструктор перемещения должен выполнять точно такой же объем работы, что и обычный конструктор копирования.

2
ответ дан 24 November 2019 в 04:34
поделиться

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

Не поймите меня неправильно: бывают случаи, когда имеет смысл передавать объекты типа коллекции (например, строки), но в приведенном примере я бы посчитал передачу или возврат вектора плохой идеей.

37
ответ дан 24 November 2019 в 04:34
поделиться

Суть такова:

Copy Elision и RVO могут избежать «страшных копий» (компилятор не требуется для реализации этих оптимизаций, а в некоторых ситуациях он не может быть применено)

Ссылки C ++ 0x RValue разрешают строковые / векторные реализации, которые гарантируют это.

Если вы можете отказаться от старых реализаций компиляторов / STL, возвращайте векторы свободно (и убедитесь, что ваши собственные объекты тоже поддерживают это). Если ваша база кода должна поддерживать «меньшие» компиляторы, придерживайтесь старого стиля.

К сожалению, это сильно влияет на ваши интерфейсы. Если C ++ 0x не подходит и вам нужны гарантии, в некоторых сценариях вы можете вместо этого использовать объекты с подсчетом ссылок или копированием при записи. Однако у них есть недостатки с многопоточностью.

(Я бы хотел, чтобы только один ответ на C ++ был простым, понятным и без условий).

18
ответ дан 24 November 2019 в 04:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: