Возврат контейнеров 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();
или есть лучший способ?
8 ответов
нет, если вы просто хотите, чтобы заполнить std::list
с элементами, то можно использовать std::fill
или std::fill_n
или комбинация стандартных функций алгоритма.
поскольку неясно, что / как именно вы хотите заполнить свой список, поэтому не можете точно комментировать свой код. Если вы не делаете что-то особенное или сложное, чего нельзя сделать со стандартными функциями алгоритма, предпочитайте использовать стандартные функции алгоритма. И если они вам не помогут, только тогда идите на первый подход и компилятор может оптимизировать возвращаемое значение в коде, устраняя ненужные копии, поскольку большинство компиляторов реализуют RVO.
посмотреть эти темы в Википедии:
и видим эти интересные темы в самом stackoverflow:
- в C++, это все-таки плохая практика, чтобы вернуть вектор из функции?
- возврат c++ std::vector без копии?
статьи Дэйв Абрахамс:
Я бы все же подчеркнул Это : вы видели все общие функции предоставлен
Я обычно использую метод 4 (почти идентичный способ 2):
void fill(ItemContainer& result) {
// fill the 'result'
}
ItemContainer a;
fill(a);
метод 1 может извлечь выгоду из копирования elision, но следующий очень похожий код не может:
ItemContainer a = CreateManyItems();
// do some stuff with a
a = CreateManyItems();
// do some different stuff with a
для этого требуется семантика перемещения C++0x, чтобы эффективно избежать копирования контейнера. Когда вы разрабатываете свою функцию, вы не знаете, как клиенты захотят ее использовать, поэтому, полагаясь на копирование elision таким образом, может поймать кого-то с неприятным хитом производительности. Их исправление в C++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));
теперь, если вызывающий предпочел бы иметь элементы в деке, потому что они хотят случайный доступ, то им просто нужно настроить свой код:
std::deque<Item> a;
CreateManyItems(std::back_inserter(a));
или, если они вообще не нужны в коллекции, они могут написать алгоритм потоковой передачи, который выполняет свою работу в своем выходном итераторе, и сохранить много памяти.
вы могли бы написать класс итератора, который генерирует элементы один за другим, но это как правило чтобы быть немного более верным, потому что поток управления "наизнанку" и не обязательно делает код вызывающих абонентов лучше (свидетель istream_iterator
).
метод 1 в порядке. Это называется copy ellision, и вы обнаружите, что он автоматически применяется для преобразования метода 1 в Метод 2, в основном, но это менее уродливо поддерживать.
связанный список? Если вы даже смутно ориентированы на производительность, используйте std::vector
.
из них номер три, вероятно, имеет лучшую производительность. Возможно, немного лучше и более гибким было бы изменение числа 2 без swap
-- как раз заполнять контейнер сразу. Конечно, это не было бы исключением-безопасно.
Инкапсулируйте контейнер в соответствующий класс, используя идиому pimpl. Затем передайте / верните этот класс по значению.
есть гораздо лучший способ и я удивлен, что ни один из ответов указывал на это. Вы в основном используете выходной итератор. Это делает вашу функцию независимой от контейнера и гораздо более гибкой.
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));
}