Возврат stl-контейнеров из функций - PullRequest
11 голосов
/ 13 мая 2011

Каков наилучший (с точки зрения производительности) возврат контейнеров 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 ]

8 голосов
/ 13 мая 2011

Нет, если вы просто хотите заполнить std::list элементами, тогда вы можете использовать std::fill или std::fill_n, или комбинацию стандартных функций алгоритма.

Поскольку неясно, что / как именно вы хотите заполнить свой список, поэтому не можете точно комментировать ваш код. Если вы не делаете что-то особенное или сложное, что невозможно сделать с помощью стандартных функций алгоритма, предпочтите использование стандартных функций алгоритма. И если они вам не помогут, только тогда перейдите к первому подходу, и компилятор может оптимизировать возвращаемое значение в вашем коде, исключая ненужные копии, поскольку большинство компиляторов реализуют RVO.

Смотрите эти темы в Википедии:

И посмотрите эти интересные темы в самом stackoverflow:

Статья Дейва Абрахамса:


Я бы все же подчеркнул это: вы видели все универсальные функции , предоставляемые заголовком <algorithm>? Если нет, то я бы посоветовал вам сначала изучить их и посмотреть, может ли какой-либо из них (или их комбинация) сделать то, что вы хотите сделать в своем коде.

Если вы хотите создать и заполнить список, вы можете использовать функцию std::generate() или std::generate_n.

6 голосов
/ 13 мая 2011

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

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

ItemContainer a;
fill(a);
4 голосов
/ 13 мая 2011

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

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

2 голосов
/ 13 мая 2011

Метод 1 может извлечь выгоду из исключения копирования, но следующий очень похожий код не может:

ItemContainer a = CreateManyItems();
// do some stuff with a
a = CreateManyItems();
// do some different stuff with a

Для этого требуется семантика перемещения C ++ 0x, чтобы эффективно избежать копирования контейнера.Когда вы разрабатываете свою функцию, вы не знаете, как клиенты захотят ее использовать, поэтому, полагаясь на элиминацию копирования, вы можете поймать кого-то с неприятным падением производительности.Их исправление в C ++ 03 будет следующим, и они должны быть готовы к ситуациям, в которых они нуждаются:

ItemContainer a = CreateManyItems();
// do some stuff with a
CreateManyItems().swap(a);

Поскольку это в основном то же самое, что и метод 2, вы можете предложить оба варианта: 1 * 1007.* и 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).

1 голос
/ 13 мая 2011

Из них номер три, вероятно, имеет лучшую производительность.Возможно, немного лучше и более гибким будет вариант № 2 без swap - просто наполнение контейнера напрямую.Конечно, это не было бы безопасно для исключений.

1 голос
/ 13 мая 2011

Метод 1 в порядке.Он называется copy ellision, и вы обнаружите, что он автоматически применяется для преобразования метода 1 в метод 2, в основном, но его обслуживание менее уродливо.

Связанный список?Если вы хоть немного ориентированы на производительность, используйте std::vector.

0 голосов
/ 26 января 2018

Существует гораздо лучший способ, и я удивлен, что ни один из ответов не указал на это.Вы в основном используете выходной итератор.Это делает вашу функцию независимой от контейнера и намного более гибкой.

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));
}
0 голосов
/ 13 мая 2011

Инкапсулируйте контейнер в соответствующем классе, используя выражение pimpl.Затем передайте / верните этот класс по значению.

...