Подтвердить что ты не робот

Лучший способ вернуть список объектов на С++?

Прошло некоторое время с тех пор, как я запрограммировал программу на С++, и после выхода из python я чувствую себя в моде в прямом пиджаке, хорошо, я не собираюсь говорить.

У меня есть пара функций, которые действуют как "трубы", принимают список как вход, возвращают другой список в качестве вывода (на основе ввода),

это в концепции, но на практике я использую std::vector для представления списка, это приемлемо?

далее, я не использую никаких указателей, поэтому я использую std::vector<SomeType> the_list(some_size); как переменную и возвращаю ее напрямую, т.е. return the_list;

P.S. Пока все в порядке, размер проекта мал, и это, похоже, не влияет на производительность, но я все же хочу получить несколько советов по этому поводу, потому что мне кажется, что я пишу python на С++.

4b9b3361

Ответ 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 "обменивается" и разрушается.

Это эффективный способ выполнения задания.