2013-03-29 3 views
16

Сколько данных копируется при возврате std :: vector в функцию и насколько большой оптимизацией будет размещение std :: vector в свободном хранилище (в куче) и возвращает указатель вместо т.е. является:Эффективный способ возврата std :: vector в C++

std::vector *f() 
{ 
    std::vector *result = new std::vector(); 
    /* 
    Insert elements into result 
    */ 
    return result; 
} 

более эффективен, чем:

std::vector f() 
{ 
    std::vector result; 
    /* 
    Insert elements into result 
    */ 
    return result; 
} 

?

+1

Как насчет передачи вектора по ссылке, а затем заполнения его внутри 'f'? –

+2

[RVO] (http://en.wikipedia.org/wiki/Return_value_optimization) - довольно простая оптимизация, которую большинство компиляторов сможет сделать в любой момент. –

+0

По мере прохождения ответов он может помочь вам определить, используете ли вы C++ 03 или C++ 11. Наилучшие практики между двумя версиями варьируются довольно немного. –

ответ

24

В C++ 11, это является предпочтительным способом:

std::vector<X> f(); 

То есть, возврат по значению.

С C++ 11, std::vector имеет вселение семантики, что означает местный вектор, объявленный в функции будет переехал по возвращению, а в некоторых случаях даже движение может быть Опущенными компилятором.

+0

Будет ли это перемещено даже без 'std :: move'? –

+4

@LeonidVolnitsky: Да, если это * местный *. Фактически, 'return std :: move (v);' отключит move-elision, даже если это возможно с помощью только 'return v;'. Поэтому последнее предпочтительнее. – Nawaz

0

Общей идиомой является передача ссылки на заполняемый объект.

Тогда нет копирования вектора.

void f(std::vector & result) 
{ 
    /* 
    Insert elements into result 
    */ 
} 
+0

Это не более идиома в C++ 11. – Nawaz

+0

@Nawaz Я согласен. Я не уверен, что лучше всего сейчас на SO относительно вопросов на C++, но не специально C++ 11. Я подозреваю, что должен быть склонен давать ответы на C++ 11 студенту, ответы на C++ 03 - кому-то, что написано в производственном коде. У вас есть мнение? –

+3

На самом деле, после выпуска C++ 11 (который составляет 19 месяцев), я рассматриваю каждый вопрос как вопрос C++ 11, если он явно не задан как вопрос C++ 03. – Nawaz

1

Это время я отправляю ответ о RVO, мне тоже ...

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

0

Если компилятор поддерживает Named Return Value Optimization (http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx), вы можете сразу вернуть вектор обеспечения, что нет:

  1. Различных пути возвращающих разным именованным объектам
  2. Множественных путей возврата (даже если тот же именованный объект возвращается на все пути) с введенными состояниями EH.
  3. Возвращенный названный объект ссылается на встроенный блок asm.

NRVO оптимизирует избыточный конструктор копий и вызовы деструктора и, таким образом, улучшает общую производительность.

В вашем примере не должно быть реальной разницы.

16

Вы должны вернуться по значению.

Стандарт имеет специальную функцию, повышающую эффективность возврата по значению. Это называется «копирование elision», а точнее в этом случае «именованная оптимизация значений (NRVO)».

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

Когда NRVO применяется, то не будет никакого копирования в следующем коде:

std::vector<int> f() { 
    std::vector<int> result; 
    ... populate the vector ... 
    return result; 
} 

std::vector<int> myvec = f(); 

Но пользователь может хотеть сделать это:

std::vector<int> myvec; 
... some time later ... 
myvec = f(); 

Copy элизия не мешает копию здесь потому что это назначение, а не инициализация. Тем не менее, вы должны еще return by value. В C++ 11 назначение оптимизируется чем-то другим, называемым «семантикой перемещения». В C++ 03 приведенный выше код вызывает копию, и хотя теоретически оптимизатор может его избежать, на практике это слишком сложно. Таким образом, вместо myvec = f(), в C++ 03 вы должны написать следующее:

std::vector<int> myvec; 
... some time later ... 
f().swap(myvec); 

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

template <typename OutputIterator> void f(OutputIterator it) { 
    ... write elements to the iterator like this ... 
    *it++ = 0; 
    *it++ = 1; 
} 

Вы можете тогда также поддерживать существующий вектор-интерфейс на вершине, что:

std::vector<int> f() { 
    std::vector<int> result; 
    f(std::back_inserter(result)); 
    return result; 
} 

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

+0

+1 для подробного ответа, намного лучше, чем у меня. – Nawaz

+0

Упрощенный самый лучший и подробный ответ. Однако в вашем варианте swap() (** для C++ 03 без NRVO **) у вас все равно будет одна копия-конструктор, сделанная внутри f(): от переменной _result_ до скрытого временного объекта, который, наконец, будет заменен к _myvec_. – JenyaKh

+0

@JenyaKh: Конечно, это проблема качества реализации. Стандарт не требовал, чтобы реализация C++ 03 реализовала NRVO, так же как и не требовала включения функции. Отличие от функции inlining заключается в том, что inlining не изменяет семантику или вашу программу, тогда как NRVO делает. Портативный код должен работать с NRVO или без него. Оптимизированный код для конкретной реализации (и отдельных флагов компилятора) может искать гарантии относительно NRVO в собственной документации реализации. –

1

Как хорошо, как «return by value», возможно, это код, который может привести к ошибке. Рассмотрим следующую программу:

#include <string> 
    #include <vector> 
    #include <iostream> 
    using namespace std; 
    static std::vector<std::string> strings; 
    std::vector<std::string> vecFunc(void) { return strings; }; 
    int main(int argc, char * argv[]){ 
     // set up the vector of strings to hold however 
     // many strings the user provides on the command line 
     for(int idx=1; (idx<argc); ++idx){ 
     strings.push_back(argv[idx]); 
     } 

     // now, iterate the strings and print them using the vector function 
     // as accessor 
     for(std::vector<std::string>::interator idx=vecFunc().begin(); (idx!=vecFunc().end()); ++idx){ 
     cout << "Addr: " << idx->c_str() << std::endl; 
     cout << "Val: " << *idx << std::endl; 
     } 
    return 0; 
    }; 
  • Q: Что произойдет, когда выше выполняется? A: Корунд.
  • В: Почему компилятор не поймал ошибку? A: Потому что программа синтаксически, хотя и не семантически, правильная.
  • В: Что произойдет, если вы измените vecFunc(), чтобы вернуть ссылку? A: Программа завершается и дает ожидаемый результат.
  • В: В чем разница? A: Компилятор не должен создавать анонимные объекты и управлять ими. Программист дал указание компилятору использовать ровно один объект для итератора и для определения конечной точки, а не два разных объекта, как это сделал в сломанном примере.

выше ошибочная программа не будет указывать никаких ошибок, даже если один использует GNU г ++ варианты -Wall -Wextra -WeffC++

отчетности Если вам необходимо произвести значение, то следующий будет работать на месте вызова vecFunc() дважды:

std::vector<std::string> lclvec(vecFunc()); 
    for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)... 

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

0
vector<string> getseq(char * db_file) 

И если вы хотите распечатать его на main(), вы должны сделать это в цикле.

int main() { 
    vector<string> str_vec = getseq(argv[1]); 
    for(vector<string>::iterator it = str_vec.begin(); it != str_vec.end(); it++) { 
     cout << *it << endl; 
    } 
} 
0

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

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