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

Контейнер с фиксированным динамическим размером

Существует ли стандартный контейнер для последовательности фиксированной длины, где эта длина определяется во время выполнения. Предпочтительно, я хотел бы передать аргумент конструктору каждого элемента последовательности и использовать этот аргумент для инициализации константного члена (или ссылки). Я также хотел бы получить элемент последовательности по заданному индексу в O (1). Мне кажется, что все мои требования не могут быть выполнены одновременно.

  • Я знаю, что std::array имеет фиксированную длину, но эта длина должна быть известна во время компиляции.
  • std::vector имеет динамический размер и позволяет передавать аргументы конструктора с помощью emplace. Хотя вы можете reserve память, чтобы избежать фактического перераспределения, тип все еще должен быть подвижным, чтобы теоретически разрешить такое перераспределение, что, например, предотвращает использование констант.
  • Кроме того, есть std::list и std::forward_list, которые не требуют подвижного типа, но которые все еще имеют изменяемый размер и будут работать довольно плохо при использовании шаблонов произвольного доступа. Я также чувствую, что с такими списками могут быть значительные накладные расходы, поскольку каждый узел списка, вероятно, будет выделяться отдельно.
  • Как ни странно, std::valarray - моя лучшая ставка на данный момент, поскольку она имеет фиксированную длину и не меняет размер автоматически. Хотя есть метод resize, ваш тип не должен быть подвижным, если вы на самом деле не вызываете этот метод. Основным недостатком здесь является отсутствие пользовательских аргументов конструктора, поэтому при таком подходе инициализация константных членов невозможна.

Есть ли какая-то альтернатива, которую я пропустил? Есть ли способ настроить один из стандартных контейнеров таким образом, чтобы он удовлетворял всем моим требованиям?


Изменение: чтобы дать вам более точное представление о том, что я пытаюсь сделать, см. Этот пример:

class A {
  void foo(unsigned n);
};

class B {
private:
  A* const a;
  const unsigned i;
public:
  B(A* aa) : a(aa), i(0) { }
  B(A* aa, unsigned ii) : a(aa), i(ii) { }
  B(const std::pair<A*, unsigned>& args) : B(args.first, args.second) { }
  B(const B&) = delete;
  B(B&&) = delete;
  B& operator=(const B&) = delete;
  B& operator=(B&&) = delete;
};

void A::foo(unsigned n) {
  // Solution using forward_list should be guaranteed to work
  std::forward_list<B> bs_list;
  for (unsigned i = n; i != 0; --i)
    bs_list.emplace_front(std::make_pair(this, i - 1));

  // Solution by Arne Mertz with single ctor argumen
  const std::vector<A*> ctor_args1(n, this);
  const std::vector<B> bs_vector(ctor_args1.begin(), ctor_args1.end());

  // Solution by Arne Mertz using intermediate creator objects
  std::vector<std::pair<A*, unsigned>> ctor_args2;
  ctor_args2.reserve(n);
  for (unsigned i = 0; i != n; ++i)
    ctor_args2.push_back(std::make_pair(this, i));
  const std::vector<B> bs_vector2(ctor_args2.begin(), ctor_args2.end());
}
4b9b3361

Ответ 1

Теоретически vector обладает необходимыми свойствами. Как вы отметили, действия, которые, возможно, выполняют присвоения содержащемуся типу, включая, в частности, любые модификации последовательности (empace_back, push_back, insert и т.д.), Не поддерживаются, если элементы не подлежат копированию и/или не назначаются. Таким образом, чтобы создать вектор не подлежащих копированию элементов, вам нужно будет построить каждый элемент во время построения вектора.

Как указывает Стив Джессоп в своем ответе, если вы определяете векторный const в первую очередь, вы даже не сможете назвать такие модифицирующие действия - и, конечно, элементы остаются неизменными.

Если я правильно понимаю, у вас есть только последовательность аргументов конструктора, а не действительная последовательность объектов. Если только один аргумент и содержащийся тип имеют соответствующий конструктор, все должно быть легким:

struct C
{
  const int i_;  
  C(int i) : i_(i) {}
};

int main()
{
  const std::vector<C> theVector { 1, 2, 3, 42 };
}

Если конструктор явный, вы должны сначала составить список или явно построить объекты в списке initializer:

int main()
{
  auto list = { 1, 2, 3, 4 };
  const std::vector<C> theVector (std::begin(list), std::end(list));
  const std::vector<C> anotherVector { C(1), C(44) };
}

Если это более чем один аргумент для построенного объекта, рассмотрите промежуточный объект-создатель:

struct C
{
  const int i_;  
  C(int i, int y) : i_(i+y) {}
};

struct CCreator
{ 
  int i; int y; 
  explicit operator C() { return C(i,y); }
};

int main()
{
  const std::vector<CCreator> ctorArgs = { {1,2}, {3,42} };
  const std::vector<C> theVector { begin(ctorArgs), end(ctorArgs) };
}

Ответ 2

Я думаю, что const std::vector<T> имеет свойства, которые вы просите. Его элементы на самом деле не определены с помощью const, но он предоставляет им представление const. Вы не можете изменить размер. Вы не можете вызывать какие-либо из функций-членов, для которых нужно T быть подвижным, поэтому для нормального использования они не будут созданы (они были бы, если вы сделали объявление класса extern, так что вы не можете этого сделать).

Если я ошибаюсь, и у вас проблемы, потому что T не движется, попробуйте const std::deque<T>.

Трудность заключается в построении более яркого - в С++ 11 вы можете сделать это с помощью списка инициализаторов, или в С++ 03 вы можете построить const vector из неконстантного вектора или из чего-либо еще получить итераторы. Это не обязательно означает, что T должен быть скопирован, но должен быть тип, из которого он может быть построен (возможно, тот, который вы изобретаете для этой цели).

Ответ 3

Добавьте уровень косвенности с помощью std::shared_ptr. Общий указатель может быть скопирован и назначен как обычно, но без изменения объекта, на который указывает. Таким образом, у вас не должно быть никаких проблем, как показано в следующем примере:

class a
{
public:
    a(int b) : b(b) { }

    // delete assignment operator
     a& operator=(a const&) = delete;

private:
    // const member
    const int b;
};

// main
std::vector<std::shared_ptr<a>> container;

container.reserve(10);
container.push_back(std::make_shared<a>(0));
container.push_back(std::make_shared<a>(1));
container.push_back(std::make_shared<a>(2));
container.push_back(std::make_shared<a>(3));

Другим преимуществом является функция std::make_shared, которая позволяет создавать ваши объекты с произвольным количеством аргументов.


Edit:

Как отмечено MvG, можно также использовать std::unique_ptr. Используя boost::indirect_iterator, косвенность может быть удалена путем копирования элементов в новый вектор:

void A::foo(unsigned n)
{
    std::vector<std::unique_ptr<B>> bs_vector;
    bs_vector.reserve(n);

    for (unsigned i = 0; i != n; ++i)
    {
        bs_vector.push_back(std::unique_ptr<B>(new B(this, i)));
    }

    typedef boost::indirect_iterator<std::vector<std::unique_ptr<B>>::iterator> it;

    // needs copy ctor for B
    const std::vector<B> bs_vector2(it(bs_vector.begin()), it(bs_vector.end()));

    // work with bs_vector2
}