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

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

5 ответов

Решение

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

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

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

т.е.

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

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

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

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

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

В 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};
Другие вопросы по тегам