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

Интерфейс для возврата связки значений

У меня есть функция, которая принимает число и возвращается к тому множеству вещей (скажем, ints). Какой самый чистый интерфейс? Некоторые мысли:

  • Верните a vector<int>. Вектор будет скопирован несколько раз, что неэффективно.
  • Верните a vector<int>*. Теперь мой получатель должен выделить сам вектор, а также элементы. Есть все обычные проблемы того, кто должен освободить вектор, тот факт, что вы не можете выделить один раз и использовать одно и то же хранилище для множества разных вызовов для получателя и т.д. Поэтому алгоритмы STL обычно избегают выделения памяти, вместо этого он прошел.
  • Вернуть a unique_ptr<vector<int>>. Теперь выясняется, кто его удаляет, но у нас все еще есть другие проблемы.
  • Возьмите vector<int> в качестве эталонного параметра. Геттер может push_back(), и вызывающий может решить, нужно ли reserve() пробелу. Однако, что должен делать геттер, если переданный vector не пуст? Добавьте? Перезаписать, сначала очистив его? Утверждать, что он пуст? Было бы хорошо, если бы подпись функции допускала только одну интерпретацию.
  • Передайте итератор begin и end. Теперь нам нужно вернуть количество фактически написанных элементов (которые могут быть меньше желаемого), и вызывающему абоненту необходимо быть осторожным, чтобы не обращаться к элементам, которые никогда не записывались.
  • Получите у получателя iterator, и вызывающий может передать insert_iterator.
  • Откажитесь и просто передайте char *.:)
4b9b3361

Ответ 1

В С++ 11, где семантика перемещения поддерживается для стандартных контейнеров, следует перейти с опцией 1.

Это делает сигнатуру вашей функции прозрачной, сообщая, что вы просто хотите, чтобы вектор целых чисел был возвращен, и он будет эффективным, потому что не будет выдан экземпляр: будет создан конструктор перемещения std::vector (или, скорее всего, будет применена оптимизация с нумерованным возвратным значением, в результате чего не будет никакого перемещения и нет копии):

std::vector<int> foo()
{
    std::vector<int> v;
    // Fill in v...
    return v;
}

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

В С++ 03 вы можете пойти с опцией 4 и взять ссылку lvalue для вектора non const: стандартные контейнеры в С++ 03 не поддерживают перенос, а копирование вектора может быть дорогая. Таким образом:

void foo(std::vector<int>& v)
{
    // Fill in v...
}

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

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

Ответ 2

Вы сами написали:

... Вот почему алгоритмы STL обычно избегают выделения памяти, вместо этого они пропускают в

за исключением того, что алгоритмы STL обычно не хотят "пропускать память", вместо этого они работают с итераторами. Это специально отделить алгоритм от контейнера, создавая:

вариант 8

отделить генерирование значения от использования и хранения этих значений, возвращая итератор ввода.

Самый простой способ - использовать boost::function_input_iterator, но механизм эскиза ниже (в основном потому, что я печатал быстрее, чем думал).


Введите тип итератора

(использует С++ 11, но вы можете заменить std::function указателем на функцию или просто закодировать логику генерации):

#include <functional>
#include <iterator>
template <typename T>
class Generator: public std::iterator<std::input_iterator_tag, T> {
    int count_;
    std::function<T()> generate_;
public:
    Generator() : count_(0) {}
    Generator(int count, std::function<T()> func) : count_(count)
                                                  , generate_(func) {}
    Generator(Generator const &other) : count_(other.count_)
                                      , generate_(other.generate_) {}
    // move, assignment etc. etc. omitted for brevity
    T operator*() { return generate_(); }
    Generator<T>& operator++() {
        --count_;
        return *this;
    }
    Generator<T> operator++(int) {
        Generator<T> tmp(*this);
        ++*this;
        return tmp;
    }
    bool operator==(Generator<T> const &other) const {
        return count_ == other.count_;
    }
    bool operator!=(Generator<T> const &other) const {
        return !(*this == other);
    }
};

Пример функции генератора

(опять же, тривиально, чтобы заменить лямбда функцией вне строки для С++ 98, но это меньше печатает)

#include <random>
Generator<int> begin_random_integers(int n) {
    static std::minstd_rand prng;
    static std::uniform_int_distribution<int> pdf;
    Generator<int> rv(n,
                      []() { return pdf(prng); }
                     );
    return rv;
}
Generator<int> end_random_integers() {
    return Generator<int>();
}

Пример использования

#include <vector>
#include <algorithm>
#include <iostream>
int main()
{
    using namespace std;
    vector<int> out;

    cout << "copy 5 random ints into a vector\n";
    copy(begin_random_integers(5), end_random_integers(),
         back_inserter(out));
    copy(out.begin(), out.end(),
         ostream_iterator<int>(cout, ", "));

    cout << "\n" "print 2 random ints straight from generator\n";
    copy(begin_random_integers(2), end_random_integers(),
         ostream_iterator<int>(cout, ", "));

    cout << "\n" "reuse vector storage for 3 new ints\n";
    out.clear();
    copy(begin_random_integers(3), end_random_integers(),
         back_inserter(out));
    copy(out.begin(), out.end(),
         ostream_iterator<int>(cout, ", "));
}

Ответ 3

return vector<int>, он не будет скопирован, он будет перемещен.

Ответ 4

В С++ 11 правильным ответом является возвращение std::vector<int>, чтобы вернуть его, гарантируя, что он будет либо явно, либо неявно перемещен. (Предпочитают неявное перемещение, потому что явное перемещение может блокировать некоторые оптимизации)

Интересно, что если вы беспокоитесь о повторном использовании буфера, самым простым способом является выбор необязательного параметра, который принимает значение std::vector<int> следующим образом:

std::vector<int> get_stuff( int how_many, std::vector<int> retval = std::vector<int>() ) {
  // blah blah
  return retval;
}

и если у вас есть предварительно выделенный буфер нужного размера, просто std::move он в функцию get_stuff, и он будет использоваться. Если у вас нет предварительно выделенного буфера нужного размера, не пропустите std::vector in.

Пример в реальном времени: http://ideone.com/quqnMQ

Я не уверен, что это заблокирует NRVO/RVO, но нет основополагающей причины, почему это необходимо, и перемещение std::vector достаточно дешево, что вам, вероятно, все равно, блокирует ли он NRVO/RVO во всяком случае.

Однако вы, возможно, не хотите возвращать std::vector<int> - возможно, вы просто хотите перебирать элементы, о которых идет речь.

В этом случае есть простой способ и трудный путь.

Простым способом является метод for_each_element( Lambda ):

#include <iostream>
struct Foo {
  int get_element(int i) const { return i*2+1; }
  template<typename Lambda>
  void for_each_element( int up_to, Lambda&& f ) {
    for (int i = 0; i < up_to; ++i ) {
      f( get_element(i) );
    }
  }
};
int main() {
  Foo foo;
  foo.for_each_element( 7, [&](int e){
    std::cout << e << "\n";
  });
}

и, возможно, использовать std::function, если вы должны скрыть реализацию for_each.

Трудным путем было бы вернуть генератор или пару итераторов, которые генерируют эти элементы.

Оба из них избегают бессмысленного выделения буфера, когда вы хотите иметь дело только с элементами по одному, а если генерировать значения, о которых идет речь, это дорого (может потребоваться перемещение памяти

В С++ 98 я бы взял vector& и clear() его.