Эффективный способ возврата std::vector в С++
Сколько копий данных при возврате функции std::vector в функции и насколько большой будет оптимизация, нужно разместить std::vector в свободном хранилище (в куче) и вернуть указатель, а именно:
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
В С++ 11 это предпочтительный способ:
std::vector<X> f();
То есть, возвращаем значение.
С С++ 11, std::vector
имеет перемещение-семантику, что означает, что локальный вектор, объявленный в вашей функции, будет перемещен при возврате, а в некоторых случаях даже перемещение может быть отменено компилятором.
Ответ 2
Вы должны вернуться по значению.
Стандарт имеет определенную функцию для повышения эффективности возврата по значению. Он называется "копирование элиции", а точнее в этом случае - "Именованная оптимизация значения стоимости (NRVO)".
Компиляторам не нужно его реализовывать, но опять же компиляторам не нужно реализовывать функцию inline (или вообще выполнять какую-либо оптимизацию). Но производительность стандартных библиотек может быть довольно плохой, если компиляторы не оптимизируют, и все серьезные компиляторы реализуют 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();
Копирование elision не предотвращает копию здесь, потому что это назначение, а не инициализация. Однако вы все равно должны вернуться по значению. В С++ 11 назначение оптимизируется чем-то другим, называемым "семантикой перемещения". В С++ 03 приведенный выше код действительно вызывает копию, и хотя теоретически оптимизатор может его избежать, на практике это слишком сложно. Поэтому вместо myvec = f()
, в С++ 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
на векторе многократно, то этот шаблонный код должен быть таким же хорошим.
Ответ 3
Я отправляю ответ RVO, мне тоже...
Если вы возвращаете объект по значению, компилятор часто оптимизирует его, поэтому он не создается дважды, так как излишне строить его в функции как временный, а затем копировать. Это называется оптимизацией возвращаемого значения: созданный объект будет перемещен, а не скопирован.
Ответ 4
Распространенной идиомой до С++ 11 является передача ссылки на заполняемый объект.
Тогда нет копирования вектора.
void f( std::vector & result )
{
/*
Insert elements into result
*/
}
Ответ 5
Если компилятор поддерживает Named Return Value Optimization
(http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx), вы можете прямо вернуть вектор, указав, что нет:
- Различные пути, возвращающие разные именованные объекты
- Несколько путей возврата (даже если тот же самый именованный объект возвращается
все пути) с введенными состояниями EH.
- Возвращенный именованный объект ссылается на встроенный блок asm.
NRVO оптимизирует избыточные вызовы конструктора и деструктора и, таким образом, повышает общую производительность.
В вашем примере не должно быть реального различия.
Ответ 6
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;
}
}
Ответ 7
Да, вернитесь по значению. Компилятор может обрабатывать его автоматически.
Ответ 8
vector<string> func1() const
{
vector<string> parts;
return vector<string>(parts.begin(),parts.end()) ;
}
Ответ 9
Так же хорошо, как "возврат по значению", это тип кода, который может привести к ошибке. Рассмотрим следующую программу:
#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;
};
- В: Что произойдет, если выполнено это? A: Корунд.
- В: Почему компилятор не поймал ошибку? A: Потому что программа
синтаксически, хотя и не семантически, правильно.
- В: Что произойдет, если вы измените vecFunc(), чтобы вернуть ссылку? A: Программа заканчивается и дает ожидаемый результат.
- В: В чем разница? A: компилятор не выполняет
должны создавать и управлять анонимными объектами. Программист дал указание компилятору использовать ровно один объект для итератора и для определения конечной точки, а не два разных объекта, как это сделал в сломанном примере.
Вышеупомянутая ошибочная программа не укажет на ошибки, даже если вы используете параметры отчетности GNU g++ -Wall -Wextra -WeffС++
Если вы должны создать значение, то вместо вызова vecFunc() будет работать следующее:
std::vector<std::string> lclvec(vecFunc());
for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)...
Вышеизложенное также не создает анонимных объектов во время итерации цикла, но требует возможной операции копирования (которая в некоторых случаях может быть оптимизирована при некоторых обстоятельствах. Но ссылочный метод гарантирует, что копия не будет создана. компилятор выполнит RVO, не заменит попытку построить наиболее эффективный код, который вы можете. Если вы можете решить, нужно ли компилятору выполнять RVO, вы опережаете игру.