Возврат контейнеров 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:

статьи Дэйв Абрахамс:


Я бы все же подчеркнул Это : вы видели все общие функции предоставлен


Я обычно использую метод 4 (почти идентичный способ 2):

void fill(ItemContainer& result) {
    // fill the 'result'
}

ItemContainer a;
fill(a);

Я бы использовал метод 1 и надеюсь, что компилятор оптимизирует копию возвращаемого значения.

Именованная Оптимизация Возвращаемого Значения


метод 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));
}