Возврат контейнеров stl из функций
Каков наилучший способ (по производительности) возврата контейнеров stl из функции? Возвращенный контейнер обычно содержит несколько тысяч элементов.
Способ 1:
typedef std::list<Item> ItemContainer;
ItemContainer CreateManyItems() {
ItemContainer result;
// fill the 'result' ...
return result;
}
ItemContainer a = CreateManyItems();
Способ 2:
void CreateManyItems(ItemContainer &output) {
ItemContainer result;
// fill the 'result' ...
output.swap(result);
}
ItemContainer a;
CreateManyItems(a);
Способ 3:
void std::auto_ptr<ItemContainer> CreateManyItems() {
std::auto_ptr<ItemContainer> result(new ItemContainer);
// fill the 'result' ...
return result;
}
std::auto_ptr<ItemContainer> a = CreateManyItems();
Или есть лучший способ?
Ответы
Ответ 1
Нет, если вы просто хотите заполнить std::list
элементами, тогда вы можете использовать std::fill
или std::fill_n
или комбинацию стандартных функций алгоритма.
Как не понятно, что/как именно вы хотите заполнить свой список, так что не можете точно комментировать свой код. Если вы не делаете что-то очень особенное или сложное, что невозможно сделать со стандартными функциями алгоритма, предпочитайте использовать стандартные функции алгоритма. И если они вам не помогут, только тогда перейдите к первому подходу, и компилятор может оптимизировать возвращаемое значение в коде, возвращая ненужные копии, поскольку большинство компиляторов реализует RVO.
Смотрите эти темы в википедии:
И посмотрите эти интересные темы в самом стеке:
Статья от Дейва Абрахама:
Я бы все же подчеркнул это: видели ли вы все общие функции, предоставляемые заголовком <algorithm>
? Если нет, то я предлагаю вам сначала изучить их и посмотреть, сможет ли кто-либо из них (или их комбинации) сделать то, что вы хотите сделать в своем коде.
Если вы хотите создать и заполнить список, вы можете использовать функцию std::generate()
или std::generate_n
.
Ответ 2
Я обычно использую метод 4 (почти идентичный методу 2):
void fill(ItemContainer& result) {
// fill the 'result'
}
ItemContainer a;
fill(a);
Ответ 3
Я бы использовал метод 1 и надеюсь, что компилятор оптимизирует копию возвращаемого значения.
Именованная оптимизация возвращаемого значения
Ответ 4
Метод 1 может извлечь выгоду из копирования, но следующий очень похожий код не может:
ItemContainer a = CreateManyItems();
// do some stuff with a
a = CreateManyItems();
// do some different stuff with a
Для этого требуется семантика перемещения С++ 0x, чтобы эффективно избежать копирования контейнера. Когда вы разрабатываете свою функцию, вы не знаете, как клиенты захотят ее использовать, поэтому, опираясь на копирование, вы можете столкнуться с неприятным поражением производительности. Их исправление в С++ 03 было бы следующим: и они должны быть в курсе ситуаций, в которых они нуждаются:
ItemContainer a = CreateManyItems();
// do some stuff with a
CreateManyItems().swap(a);
Так как это в основном то же самое, что и метод 2, вы можете предлагать как 1, так и 2, как перегрузки, которые подсказывают вызывающему, что они должны думать о потенциальной ловушке производительности.
При условии, что элементы в коллекции не относятся друг к другу, моя предпочтительная форма API выглядит следующим образом, но зависит от того, какой интерфейс вы выставляете - поскольку это шаблон, реализация должна быть доступна, когда (если вы не можете заранее предсказать типы, с которыми он будет использоваться, и extern
эти специализации):
template <typename OutputIterator>
void CreateManyItems(OutputIterator out) {
*out++ = foo; // for each item "foo"
}
ItemContainer a;
CreateManyItems(std::back_inserter(a));
// do some stuff with a
a.clear();
CreateManyItems(std::back_inserter(a));
Теперь, если вызывающий абонент предпочтет иметь элементы в deque, потому что им нужен произвольный доступ, тогда им просто нужно настроить свой код:
std::deque<Item> a;
CreateManyItems(std::back_inserter(a));
Или, если они вообще не нужны в коллекции, они могут написать алгоритм потоковой передачи, который выполняет свою работу на своем итераторе вывода, и сэкономит много памяти.
Вместо этого вы можете написать класс итератора, который генерирует элементы один за другим, но это имеет тенденцию быть немного более затруднительным, потому что поток управления "наизнанку" и не обязательно делает код вызывающих лиц более приятным ( свидетель istream_iterator
).
Ответ 5
Метод 1 является прекрасным. Он называется копированием ellision, и вы обнаружите, что он автоматически применяется для преобразования метода 1 в метод 2, в основном, но он менее уродлив для поддержания.
Связанный список? Если вы даже смутно ориентированы на производительность, используйте std::vector
.
Ответ 6
Из них, номер три, вероятно, лучший результат. Возможно, немного лучше и более гибко, будет вариант номер 2 без swap
- просто заполнить контейнер напрямую. Конечно, это не было бы безопасным исключением.
Ответ 7
Инкапсулируйте контейнер в соответствующем классе, используя идиому pimpl. Затем передайте/верните этот класс по значению.
Ответ 8
Есть намного лучший способ, и я удивлен, что ни один из ответов не указал на это. В основном вы используете выходной итератор. Это делает вашу функцию независимой от контейнера и гораздо более гибкой.
template<typename OutIter>
void CreateManyItems(OutIter it)
{
//generate some data
*it++ = 1;
*it++ = 2;
*it++ = 3;
}
Вот как вы его используете:
void main()
{
//use array as output container
int arr[3];
CreateManyItems(arr);
//use vector as output container
std::vector<float> v;
CreateManyItems(std::back_inserter(v));
}