Мне нужен шаблон класса массива C ++, который имеет фиксированный размер, основан на стеке и не требует конструктора по умолчанию - PullRequest
1 голос
/ 06 октября 2010

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

Ответы [ 5 ]

1 голос
/ 07 октября 2010

Ну, я бы подумал, что кто-то мог бы дать ответ сейчас, но, похоже, нет, так что давайте.

То, о чем вы мечтаете, - это то, о чем я сам мечтал: 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 и initializer-lists vector get emplace_back непосредственное конструирование элемента на месте, что снимает требование CopyConstructible, может быть хорошим благом в зависимости от вашего случая.

0 голосов
/ 06 октября 2010

может быть хранить Boost :: вариант в вашем Boost :: массив?сделать первый параметр целым или чем-то еще.

то есть

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

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

0 голосов
/ 06 октября 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

0 голосов
/ 06 октября 2010

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

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

0 голосов
/ 06 октября 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};
...