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

Ответ 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));
}