Мне нужен шаблон класса массива C ++, который имеет фиксированный размер, основан на стеке и не требует конструктора по умолчанию.

Итак, я смотрел на boost :: array, но для этого нужен конструктор по умолчанию. Я думаю, что лучший способ заполнить этот массив данными - использовать метод push_back (const T &). Вызов его больше раз, чем SIZE (известный во время компиляции), приведет к утверждению или исключению, в зависимости от конфигурации сборки. Таким образом, он всегда будет содержать значимые данные. Кто-нибудь знает эффективную, портативную, надежную реализацию этой концепции?


person andriej    schedule 06.10.2010    source источник
comment
push_back противоречит фиксированному размеру. Думаю, я знаю, что вы имеете в виду, что у него есть фиксированный верхний предел размера, и что вы не будете менять размер после того, как заполните его. Я не знаю об этом.   -  person Steve Jessop    schedule 06.10.2010
comment
Что вы имеете в виду под не требует конструктора по умолчанию?   -  person joshperry    schedule 06.10.2010
comment
char array[5] имеет фиксированный размер, может быть шаблонизирован и основан на стеке. Бонус: работает даже в C. : D   -  person ereOn    schedule 06.10.2010
comment
@ Стив: Я думаю, он действительно имел в виду, что ему нужна фиксированная мощность.   -  person Matthieu M.    schedule 06.10.2010
comment
@ user467799: угадай, boost::array<T*> тебя не устраивает?   -  person Matthieu M.    schedule 06.10.2010
comment
@joshperry: стандартные контейнеры требуют, чтобы содержащийся тип имел конструктор без аргументов. В принципе, как часть их контракта, и на практике, если вы делаете что-то, что фактически использует этот конструктор.   -  person Steve Jessop    schedule 06.10.2010
comment
Ах, вы не хотите, чтобы он инициализировал содержащиеся объекты их конструктором по умолчанию.   -  person joshperry    schedule 06.10.2010
comment
@Matthieu Зачем вам использовать указатель для обхода конструкции по умолчанию?   -  person Šimon Tóth    schedule 06.10.2010
comment
эта ссылка, предоставленная sbi, дает статический вектор: stackoverflow.com/questions/3563591/. Я не мог его использовать, но теперь могу передать ...   -  person stefaanv    schedule 06.10.2010
comment
@Let_Me_Be: потому что указатели являются конструктивными по умолчанию (на null), и, таким образом, он работает хорошо ... хотя, к сожалению, означает выделение в бесплатном хранилище, что может быть не тем, что нравится OP.   -  person Matthieu M.    schedule 06.10.2010
comment
@Matthieu Это то, о чем я спрашивал. Зачем вам добавлять этот уровень косвенного обращения, который заставляет вас использовать динамическую память?   -  person Šimon Tóth    schedule 06.10.2010
comment
@Let_Me_be: потому что он снимает ограничение DefaultConstructible на T, что составляет суть вопроса. Не все объекты являются DefaultConstructible, показательный пример: те, которые используют распределители в boost::interprocess областях памяти.   -  person Matthieu M.    schedule 06.10.2010
comment
@ Стив Джессоп: Нет. Когда вы создаете непустой контейнер (или изменяете его размер), вы передаете экземпляр, который копируется в эти новые слоты. Созданный по умолчанию объект предоставляется в качестве аргумента по умолчанию, но для класса, который не поддерживает построение по умолчанию, вы можете передать что-нибудь еще.   -  person Jerry Coffin    schedule 06.10.2010
comment
@Jerry: извините, вы конечно правы. Конструктор no-args по контракту не требуется, я не знаю, почему я так себе вообразил.   -  person Steve Jessop    schedule 06.10.2010
comment
@Matthie Но вам не нужно косвенное обращение, так зачем его добавлять?   -  person Šimon Tóth    schedule 06.10.2010
comment
@Mattheiu M. Но задающий вопрос запрашивает контейнер на основе стека (в заголовке и тегах, но не в теле вопроса). Размещение массива указателей в стеке основано на стеке, но если объекты, которые спрашивающий хочет, чтобы они действительно находились в контейнере, вообще не находятся в контейнере или в стеке, то я думаю, что вы отошли от духа вещи ...   -  person Steve Jessop    schedule 06.10.2010
comment
@ Стив Джессоп: Я подозреваю, что вы так думали, потому что в каком-то смысле были правы. В итоге вы получаете «объект по умолчанию». Разница в том, что вы можете указать значение по умолчанию для любой данной операции вместо того, чтобы указывать одно значение по умолчанию для всех времен как часть дизайна класса.   -  person Jerry Coffin    schedule 06.10.2010


Ответы (5)


Что ж, я бы подумал, что кто-то принесет ответ сейчас, но, похоже, нет, так что поехали.

То, чего вы желаете, это то, о чем я мечтал: a boost::optional_array<T,N>.

Есть два варианта:

  • Первое: аналогично boost::array< boost::optional<T>, N >, то есть каждый элемент может быть установлен или не установлен.
  • Второе: похоже на std::vector<T> (как-то), то есть все начальные элементы установлены, а все последующие - нет.

Учитывая предыдущие вопросы / комментарии, кажется, вы хотели бы второй, но на самом деле это не имеет значения, поскольку оба они очень похожи.

template <typename T, size_t N>
class stack_vector
{
public:
  bool empty() const { return mSize == 0; }
  size_t size() const { return mSize; }
  size_t capacity() const { return N; }
  size_t max_size() const { return N; }

  T& operator[](size_t i) { return *(this->pfront() + i); }
  /// ...

private:
  T* pfront() const { return reinterpret_cast<T*>(&mStorage); }

  std::aligned_storage< N * sizeof(T), alignof(T) > mStorage;
  size_t mSize; // indicate how many elements are set, from the beginning
};

Давайте сосредоточимся на этих очень специальных операциях:

template <typename T, size_t N>
void push_back(T const& t)
{
  new (this->pfront() + mSize) T(t); // in place construction
  ++mSize;
}

template <typename T, size_t N>
void clear()
{
  for (size_t i = 0; i != mSize; ++i)
  {
    (this->pfront() + i)->~T();
  }
  mSize = 0;
}

Как видите, основная трудность заключается в том, чтобы помнить следующее:

  • если там еще не построен ни один элемент, вам нужно разместить конструкцию new + copy вместо присваивания.
  • элементы, которые становятся «устаревшими» (то есть будут после последнего элемента), должны быть должным образом уничтожены (то есть их деструктор должен быть вызван).

В традиционном контейнере STL есть много операций, которые может быть сложно реализовать. На vector перетасовка элементов (из-за insert или erase), возможно, является наиболее ярким примером.

Также обратите внимание, что с C ++ 0x и списками инициализаторов vector get emplace_back для непосредственного создания элемента на месте, таким образом отменяя требование CopyConstructible, может быть хорошим благом в зависимости от вашего случая.

person Matthieu M.    schedule 07.10.2010
comment
Я отмечаю это как ответ, но я действительно не могу сказать наверняка, могут ли быть какие-либо потенциальные проблемы с этим подходом (помимо очевидного std :: align_storage, который еще может поддерживаться не всеми компиляторами). - person andriej; 08.10.2010

boost::array<T, 12> ta; ничем не отличается от T[12] ta;; если вы не используете список инициализаторов, элементы будут созданы по умолчанию.

Обычный обходной путь - boost::array<T*, 12> ta; или, может быть, boost::array<unique_ptr<T>, 12> ta;.

Единственный способ сохранить по значению - скопировать, никак иначе ... Вот что делают списки инициализаторов:

struct A {
    A(int i):_i(i){ cout << "A(int)" << endl; }
    A(const A& a){ cout << "A(const A&)" << endl; }
    ~A(){ cout << "~A()" << endl; }

    int _i;
};

int main(){
    boost::array<A, 2> ta = {{1, 2}};
}

Это выводит:

A(int)
A(const A&)
A(int)
A(const A&)
~A()
~A()
~A()
~A()

http://codepad.org/vJgeQWk5

person joshperry    schedule 06.10.2010
comment
Нет, это не оптимально для моих нужд. Я хочу хранить несколько небольших объектов в небольших массивах по значению, а не по адресу. - person andriej; 06.10.2010
comment
В некоторых сценариях использование списка инициализаторов может быть обременительным. Вы можете легко изменить размер массива, изменив константу времени компиляции в одном месте, но, вероятно, вам придется изменить списки инициализаторов во многих других местах. - person andriej; 06.10.2010
comment
Я не сказал, что это не будет громоздко, я просто говорю, что это единственный способ делать то, что вы хотите (за исключением взлома непрозрачного типа, рекомендованного в другом ответе). - person joshperry; 06.10.2010

можно сохранить boost :: variant в вашем boost :: array? сделайте первый параметр int или что-то в этом роде ..

i.e.

boost::array<boost::variant<int, foo>, 6> bar;

хорошо, вы должны иметь дело с вариантом, но он выделен стеком ...

person Nim    schedule 06.10.2010
comment
Это решение, но далеко не оптимальное. Я полагаю, что класс boost :: optional является свидетельством того, что то, что я хочу, выполнимо - вероятно, реализация boost :: optional может быть расширена до массива. - person andriej; 07.10.2010
comment
не знал об этом до сих пор, просто прочтите об этом ... аккуратно! спасибо за указатель. Это чище, чем выше ... - person Nim; 07.10.2010
comment
@ user467799: Конечно, это выполнимо. Но вам, наверное, придется самому написать такой класс. Остерегайтесь проблем с выравниванием. - person sellibitze; 07.10.2010

В C ++ 0x вы получили std::array<type, size> (вероятно, то же самое, что и boost :: array). Вы можете инициализировать данные массива, используя fill() или std::fill_n():

std::array<int, 30> array;
array.fill(0);
boost::array<int, 30> barray;
std::fill_n(barray.begin(), 30, 0);

Если вы хотите, чтобы он был инициализирован по умолчанию при определении, вы можете использовать copy-ctor:

static std::array<int, 30> const nullarray = {0, 0, 0, ..., 0}; // nullarray.fill(0);
// (...)
std::array<int, 30> array{nullarray};
person erjot    schedule 06.10.2010
comment
Если вместо int вы используете определяемый пользователем тип, тогда все 30 будут построены по умолчанию (при использовании fill), чего OP не хочет. - person joshperry; 06.10.2010
comment
@joshperry: о, вот что означает doesn't require default constructor ...; f Итак, проблема не в контейнере - тогда OP должен удалить свой default-ctor или сделать его закрытым и, например, используйте sfinae для default-ctor-check - person erjot; 06.10.2010

Почему он должен находиться в стеке? Есть ли у вас эмпирические доказательства того, что создание и reserve создание vector слишком медленно (использование vector кажется очевидным ответом)?

Даже если это так, вы можете создать пул векторов с зарезервированным пространством и swap один из заранее выделенных векторов в локальную копию. Когда вы закончите с локальным, снова поменяйте его местами (очень похоже на трюк splice с lists).

person Mark B    schedule 06.10.2010
comment
Я не голосовал против, но мне кажется, что это был бы отличный кандидат на комментарий. - person sellibitze; 06.10.2010