Причина, по которой это не работает для типов без конструкторов по умолчанию, заключается в следующей строке:
m_Data = new Type[m_Capacity];
Приведенная выше строка принципиально делает две вещи: выделяет достаточно памяти для хранения m_Capacity
экземпляров Type
, затем создайте каждый Type
так, чтобы они были готовы к использованию.Поскольку на самом деле вы не можете предоставить аргументы конструктора через этот синтаксис new[]
, при его использовании требуются конструкторы по умолчанию.
Способ, которым std::vector
(и другие стандартные контейнеры) справляются с этим, заключается в разделениипроцесс выделения памяти и процесс строительства.То есть std::vector
амортизирует стоимость выделения памяти, запрашивая большие порции памяти с «ничем» в ней.Затем std::vector
использует размещение new
для создания объектов непосредственно в этой памяти.
Так что-то подобное может происходить внутри std::vector
:
// HUGE SIMPLICATION OF WHAT HAPPENS!!!
// EXPOSITION ONLY!!!
// NOT TO BE USED IN ANY PRODUCTION CODE WHATSOEVER!!!
// (I haven't even considered exception safety, etc.)
template<typename T>
class Vector
{
private:
T* allocate_memory(std::size_t numItems)
{
// Allocates memory without doing any construction
return static_cast<T*>(::operator new(sizeof(T)*numItems));
}
void deallocate_memory()
{
::operator delete(buffer);
}
// ...
public:
void push_back(const T& obj)
{
if(theresNotEnoughRoom()) {
std::size_t newCapacity = calculateNewCapacity();
T* temp = allocate_memory(newCapacity);
copyItemsToNewBuffer(temp);
deallocate_memory(buffer);
buffer = temp;
bufferEnd = temp+newCapacity;
}
new (bufferEnd) T(obj); // Construct a new instance of T at end of buffer.
++bufferEnd;
}
void pop_back()
{
if(size() > 0) {
--bufferEnd;
bufferEnd->~T();
}
}
// ...
private:
T* buffer;
T* bufferEnd;
// ...
};
То, что здесь происходит, заключается в том, что наш гипотетический класс Vector
выделяет относительно большой кусок памяти, а затем, когда элементы помещаются или вставляются, класс размещает новые элементы в памяти.Таким образом, это устраняет требование конструктора по умолчанию, так как мы на самом деле не создаем никаких объектов, если только об этом не попросит вызывающая сторона.
Как вы уже можете видеть, класс std::vector
должен сделать довольно много бухгалтерии, чтобычто он делает эффективно и безопасно.Вот почему мы призываем людей использовать стандартные контейнеры вместо того, чтобы выкатывать свои собственные, если вы действительно не знаете, что делаете.Создание эффективного, безопасного и полезного векторного класса - это огромное обязательство.
Чтобы понять, что с этим связано, взгляните на статью под названием "Исключительная безопасность: концепции иТехника "" Бьярна Страуструпа, в которой обсуждается реализация "простого вектора" (раздел 3.1).Вы увидите, что это не тривиальная вещь для реализации.