2010-09-13 2 views
10

Можно создать дубликат:
how to “return an object” in C++C вектор ++, обратный по сравнению с параметром

мне интересно, если есть разница между тремя следующими подходами:

void FillVector_1(vector<int>& v) { 
    v.push_back(1); // lots of push_backs! 
} 

vector<int> FillVector_2() { 
    vector<int> v; 
    v.push_back(1); // lots of push_backs! 
    return v; 
} 

vector<int> FillVector_3() { 
    int tab[SZ] = { 1, 2, 3, /*...*/ }; 
    return vector<int>(tab, tab + SZ); 
} 
+1

в исполнении? – Anycorn

+4

Просто возвращайтесь по цене и беспокоитесь о производительности, когда чувствуете, что это на самом деле проблема. Затем профиль, чтобы вам не приходилось гадать. Я упреждающе закрываю [дубликат] (http://stackoverflow.com/questions/3350385/how-to-return-an-object-in-c). – GManNickG

+1

Я бы не проголосовал, чтобы закрыть это как дубликат по нескольким причинам: (1) Более ранний вопрос имел особенности распределения динамической памяти, которые не применяются к этому вопросу, и (2) этот вопрос является специфичным для STL, который может вызывать другие идиомы, такие как вставки итераторов. Кроме того, некоторые реализации STL уже используют ссылки на C++ 0x r-value для минимизации копирования. Этот метод не применяется в предыдущем вопросе. –

ответ

16

Самое большое отличие состоит в том, что первый способ добавляется к существующему содержимому, тогда как другие два заполняют пустой вектор. :)

Я думаю, что ключевое слово, которое вы ищете, это return value optimization, что должно быть довольно распространенным (с G ++ вам придется отключить его, чтобы предотвратить его применение). То есть, если использование, как:

vector<int> vec = fill_vector(); 

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

Если вы работаете с существующим вектором

vector<int> vec; 
while (something) 
{ 
    vec = fill_vector(); 
    //do things 
} 

затем с помощью параметра из бы избежать создания векторов в цикле и копирование данных вокруг.

+1

+1 для указания разницы между инициализацией и присваиванием w.r.t. копировать. – sellibitze

2

сначала определенно не копирует вектор.

Стоимость копирования вектора может быть линейной по числу элементов в векторе.

Первый не представляет риска линейного поведения на любой платформе или компиляторе и не требует затрат на профилирование и рефакторинг.

+0

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

6

Можно было бы считать, что параметр лучше всего, но на практике его часто нет. Это зависит от компилятора. Некоторые компиляторы (я думаю, на самом деле самые последние компиляторы) будут применять Return Value Optimization - Visual Studio 2005 и позже должны сделать это в обоих случаях, которые вы предоставили (см. Named Return Value Optimization in Visual C++ 2005).

Лучший способ узнать наверняка - проверить произведенную разборку.

+0

Это оптимизация _huge_, и большинство современных компиляторов используют ее с большим успехом в производительности. – fbrereto

+0

Вся идея RVO заставляет меня съеживаться. Я ненавижу это. –

0

С первого взгляда первые два, вероятно, имеют большее изменение размера вектора, тогда как третье, возможно, не нуждается в изменении размера вектора при его запуске. Это можно смягчить, изменив его размер до откатов.

5

Добавление четвертого варианта в смеси:

void FillVector_4(vector<int>& v) { 
    static const int tab[SZ] = {1,2,3, ... }; 
    v.assign(tab,tab+SZ); 
} 

Если вы думаете о производительности версии 2 и версии 3 может сделать компилятор создать vector<int> копию для возвращаемого значения. То есть, если компилятор не в состоянии выполнить NRVO (называемая оптимизацией возвращаемого значения). Кроме того, последовательный push_back s без reserve, вероятно, приводит к паре перераспределений, так как вектор должен расти. Неважно, зависит ли это от вашей проблемы, которую вы пытаетесь решить.

Вы будете рады узнать, что C++ 0x сделает возврат локально созданного вектора очень эффективным. Я также рекомендую прочитать David Abrahams article series об эффективных типах значений, включая передачу/возвращение.

9

идиоматические С подходом ++, было бы абстрактным над типом контейнера с помощью итератора вывода:

template<typename OutputIterator> 
void FillContainer(OutputIterator it) { 
    *it++ = 1; 
    ... 
} 

Затем он может быть использован с вектором, как:

std::vector<int> v; 
FillContainer(std::back_inserter(v)); 

производительности (и другим преимущества, такие как возможность заполнения непустого контейнера) такие же, как и для вашего варианта №1. Другое дело, что это можно использовать для вывода в потоковом режиме, где результаты немедленно обрабатываются и отбрасываются без сохранения, если используется соответствующий тип итератора (например, ostream_iterator).

+0

+1. Это похоже на самый «путь STL». Обратите внимание, что было бы полезно вернуть итератор из 'FillContainer'. –

+0

Как это работает, когда «FillContainer» - это метод класса? – User

+0

@User: то же самое - функции членов класса также могут быть шаблонами. То, что вы не можете сделать с такой вещью, - сделать ее виртуальной. –

Смежные вопросы