The reason why this doesn't work for types without default constructors is because of this line:
m_Data = new Type[m_Capacity];
The above line fundamentally does two things: allocate enough memory to hold m_Capacity
instances of Type
, then constructing each Type
so that they're ready to use. Since you can't actually provide any constructor arguments through this new[]
syntax, default constructors are required when you use this.
The way std::vector
(and the other standard containers) deals with this is by separating the memory allocation process and the construction process. That is, std::vector
amortizes the cost of memory allocation by requesting large chunks of memory with "nothing" in it. Then std::vector
uses placement new
to construct objects directly in that memory.
So something like this might be going on inside a 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;
// ...
};
So what's going on here is that our hypothetical Vector
class allocates a relatively large slab of memory, then as items are pushed or inserted, the class does placement new in the memory. So this eliminates the default constructor requirement, since we don't actually construct any objects unless requested by the caller.
As you can already see, a std::vector
class needs to do quite a bit of bookkeeping to make what it does efficient and safe. That's why we urge people to use the standard containers instead of rolling out your own, unless you really know what you're doing. Making an efficient, safe, and useful vector class is a huge undertaking.
For an idea of what's involved, take a look at a paper called "Exception Safety: Concepts and Techniques" by Bjarne Stroustrup which discusses a "simple vector" implementation (section 3.1). You'll see that it's not a trivial thing to implement.