2013-02-15 6 views
24

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

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

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


Edit: Чтобы дать вам более точное представление о том, что я пытаюсь сделать, увидеть этот пример:

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()); 
} 
+0

Итак, вы в основном хотите абсолютно непреложный контейнер? – leftaroundabout

+0

@leftaroundabout Нет, я полагаю, он просто хочет что-то вроде вектора, который никогда не перемещает его хранилище (и это свойство должно быть * static *, то есть известно во время компиляции). –

+0

Я знаю, что однажды сохранил типы в 'std :: vector', которые не были перемещены. Это работало до тех пор, пока я не использовал 'resize()'. Однако я не уверен, что это поведение переносимо. – cschwan

ответ

8

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

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

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

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

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

Если конструктор явно, вы должны составить список первым или явно построить объекты в инициализаторах-листе:

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) }; 
} 
+0

Это, по-видимому, не является ответом на вопрос, но AFAICS выполняет все требования (работает, даже если все специальные функции-члены явно удалены) и не имеет никаких оговорок, таких как служебные служебные данные. +1. – leftaroundabout

+0

Ну, это ответ на обе части вопроса: ** ** ** Он не пропустил альтернативы, так как вектор подходит. ** 2 ** Он может «отрегулировать» один из стандартных контейнеров, не используя неправильные методы, такие как emplace_back и т. Д. ;-) –

+0

В моем случае списки статических инициализаторов не применимы, так как длина будет известна только во время выполнения. Однако идея использования версии итераторной пары конструктора 'vector' хорошо работает для меня, так как это делает автоматическое преобразование. Обновлен мой вопрос с помощью примера кода. Одна из проблем заключается в том, что у меня должен быть отдельный «вектор» для аргументов конструктора, который останется в области действия после того, как я закончил использовать его, поэтому я, скорее всего, построю некоторую функцию aroud, которая возвращает законченный вектор. Или пользовательские итераторы последовательности. Если только C++ 11 имел диапазоны ... – MvG

2

Добавить уровень косвенности б y с использованием 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 
} 
+1

'shared_ptr' чувствует себя как много накладных расходов здесь. Я думаю, 'unique_ptr' должно быть достаточным, так как мне не нужны вещи для копирования, просто подвижные. Накладные расходы на управление памятью должны быть похожими на решения на основе списков, но доступ - O (1), поэтому определенно выигрыш там. Одна из проблем заключается в том, что вам нужно добавить один уровень разыменования при каждом доступе, поэтому это не замена для контейнеров, ориентированных на объекты, а вместо этого требует внесения изменений ко всему коду, обращающемуся к этому контейнеру. – MvG

+0

MvG: Я отредактировал свой ответ и включил некоторый код о том, как удалить косвенность. – cschwan

+0

MvG: Зачем вам нужен член const? – cschwan

5

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

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

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

+0

thx для 'const' - добавит это к моему ответу. –

+0

* Что-нибудь еще, что вы можете получить итераторы для * части, особенно полезно. Арне Мерц дал более подробную информацию об этом, но основная идея присутствует в обоих ваших ответах, и я считаю, что это можно сделать хорошо работать, как только я скрою эту уродство инициализации где-то за кулисами. – MvG

Смежные вопросы