Лучший способ вернуть список объектов на С++?
Прошло некоторое время с тех пор, как я запрограммировал программу на С++, и после выхода из python я чувствую себя в моде в прямом пиджаке, хорошо, я не собираюсь говорить.
У меня есть пара функций, которые действуют как "трубы", принимают список как вход, возвращают другой список в качестве вывода (на основе ввода),
это в концепции, но на практике я использую std::vector
для представления списка, это приемлемо?
далее, я не использую никаких указателей, поэтому я использую std::vector<SomeType> the_list(some_size);
как переменную и возвращаю ее напрямую, т.е. return the_list;
P.S. Пока все в порядке, размер проекта мал, и это, похоже, не влияет на производительность, но я все же хочу получить несколько советов по этому поводу, потому что мне кажется, что я пишу python на С++.
Ответы
Ответ 1
Единственное, что я вижу, это то, что вы вынуждаете копию списка, который вы возвращаете. Было бы более эффективно сделать что-то вроде:
void DoSomething(const std::vector<SomeType>& in, std::vector<SomeType>& out)
{
...
// no need to return anything, just modify out
}
Поскольку вы проходите в списке, который хотите вернуть, вы избегаете дополнительной копии.
Изменить: это старый ответ. Если вы можете использовать современный компилятор С++ с семантикой перемещения, вам не нужно беспокоиться об этом. Конечно, этот ответ по-прежнему применяется, если объект, который вы возвращаете, не имеет семантики перемещения.
Ответ 2
Если вам действительно нужен новый список, я просто верну его. Оптимизация возвращаемого значения в большинстве случаев позаботится о ненужных копиях, и ваш код останется очень ясным.
При этом, принимая списки и возвращая другие списки, на самом деле программирование на Python на С++.
A, для С++ более подходящей парадигмой будет создание функций, которые принимают ряд итераторов и изменяют базовую коллекцию.
например.
void DoSomething(iterator const & from, iterator const & to);
(с итератором, возможно, являющимся шаблоном, в зависимости от ваших потребностей)
Цеповые операции - это вопрос вызова последовательных методов на begin(), end().
Если вы не хотите изменять ввод, сначала сделайте копию.
std::vector theOutput(inputVector);
Все это происходит из философии С++ "не платите за то, что вам не нужно", вы создадите копии только там, где вы действительно хотите сохранить оригиналы.
Ответ 3
Я бы использовал общий подход:
template <typename InIt, typename OutIt>
void DoMagic(InIt first, InIt last, OutIt out)
{
for(; first != last; ++first) {
if(IsCorrectIngredient(*first)) {
*out = DoMoreMagic(*first);
++out;
}
}
}
Теперь вы можете назвать его
std::vector<MagicIngredients> ingredients;
std::vector<MagicResults> result;
DoMagic(ingredients.begin(), ingredients.end(), std::back_inserter(results));
Вы можете легко менять используемые контейнеры без изменения используемого алгоритма, а также эффективно не создавать накладных расходов в возвращаемых контейнерах.
Ответ 4
Если вы хотите быть действительно хардкором, вы можете использовать boost:: tuple.
tuple<int, int, double> add_multiply_divide(int a, int b) {
return make_tuple(a+b, a*b, double(a)/double(b));
}
Но так как кажется, что все ваши объекты имеют один, не полиморфный тип, то std::vector все хорошо и хорошо.
Если ваши типы были полиморфными (унаследованные классы базового класса), вам понадобится вектор указателей, и вам нужно будет запомнить все выделенные объекты, прежде чем выбросить ваш вектор.
Ответ 5
Использование std::vector является предпочтительным способом во многих ситуациях. Он гарантированно использует последовательную память и поэтому приятен для кеша L1.
Вы должны знать, что происходит, когда ваш тип возврата std::vector. Что происходит под капотом, так это то, что std::vector рекурсивно скопирован, поэтому, если конструктор копирования SomeType является дорогостоящим, оператор возврата может быть длительным и трудоемким.
Если вы ищете и вставляете много в свой список, вы можете посмотреть на std:: set, чтобы получить логарифмическую временную сложность вместо линейной. (std::vectors вставка постоянна до тех пор, пока ее мощность не будет превышена).
Вы говорите, что у вас много "функций труб"... звучит как отличный сценарий для std:: transform.
Ответ 6
Другая проблема с возвратом списка объектов (в отличие от работы над одним или двумя списками на месте, как указывал BigSandwich), - это если у ваших объектов есть сложные конструкторы копирования, они будут вызывать для каждого элемента в контейнере.
Если у вас есть 1000 объектов, каждый из которых ссылается на кучу памяти, и они копируют эту память в Object a, b; а = Ь; что 1000 memcopys для вас, просто для их возврата, содержащихся в контейнере. Если вы все равно хотите напрямую вернуть контейнер, подумайте о указателях в этом случае.
Ответ 7
Это работает очень просто.
list<int> foo(void)
{
list<int> l;
// do something
return l;
}
Теперь получение данных:
list<int> lst=foo();
Полностью оптимален, поскольку компилятор знает, как оптимизировать конструктор lst well. а также
не будет вызывать копии.
Другой метод, более портативный:
list<int> lst;
// do anything you want with list
lst.swap(foo());
Что происходит: foo уже оптимизирован, поэтому нет необходимости возвращать значение. когда
вы вызываете swap, вы устанавливаете значение lst на new и, таким образом, не копируете его. Теперь старое значение
из lst "обменивается" и разрушается.
Это эффективный способ выполнения задания.