Last updated:2026-10-12
C++ Avoiding the Default Constructor¶
In C++, when some containers are resized via the resize() method, the default constructor gets called, which may not be the behavior we want. This article shows how to use a custom allocator to control that process and avoid invoking default construction.
Example 1:
Do you know what the output is at this point?
The answer is:
A few things need to be understood here:
First: why did the data become 0 after being resized back? What happened?
Second: what if I want the original data back? That is, I want the output to be:
Third: what happens if the vector's element type has no default constructor?
Fourth: what are the limitations of resize?
So in this section let's study this powerful feature and see how to make resize avoid calling the default constructor.
Note: space is limited — all the code for the lazy version of this section is updated in the Knowledge Planet.
1. A Look Inside resize¶
To keep this article smooth, I use the example below: forbid Foo's construction, then call resize on a vector of it.
This produces a compile error, and we can see a pile of error messages.
First problem: you can see the call stack reaching __uninitialized_default_n, which then checks whether the current type is trivial — this is the first limitation of resize!
required from '_ForwardIterator std::__uninitialized_default_n(_ForwardIterator, _Size) [with _ForwardIterator = Foo*; _Size = long unsigned int]'
710 | return __uninitialized_default_n_1<__is_trivial(_ValueType)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
711 | && __can_fill>::
Second problem: since we forbade default construction, the allocator fails when it placement-news a default-constructed object at the original address, and an error is reported — this is the second limitation!
gcc/14.1.0_1/include/c++/14/bits/stl_construct.h:119:7: error: use of deleted function 'Foo::Foo()'
119 | ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
| ^~~~~~~~~~~~~~~~~~~
With that said, we can move on to the main body: when resize runs, the allocator is used to allocate memory; when the size passed in is greater than size (assume it is within capacity, i.e. the addresses are valid), the allocator placement-news an object constructed with the default constructor.
The gcc source implementation can be shown here — simple, right? It just calls construct.
__try
{
typedef __gnu_cxx::__alloc_traits<_Allocator> __traits;
for (; __n > 0; --__n, (void)++__cur)
__traits::construct(__alloc, std::__addressof(*__cur));
return __cur;
}
__catch(...)
{
std::_Destroy(__first, __cur, __alloc);
__throw_exception_again;
}
Alright, the principle of resize is now clear; let's revisit the questions from the beginning.
2. Avoiding the Call to the Default Constructor¶
How do we avoid calling the default constructor?
It is actually quite simple: block gcc's default construct. But how do we block it?
Here is where vector's second parameter comes into play: the allocator!
3. The Allocator¶
To solve this problem, we can use a custom allocator. A custom allocator can control how objects are constructed, letting us choose whether resize uses default construction. Below is a simple custom allocator implementation, showing only the core code.
template <class T>
class no_init_alloc {
public:
using value_type = T;
// Override construct method to do nothing
template <class U>
void construct(U* p) {
// Do nothing, memory is not initialized
}
};
We only need construct to do nothing; then we can use it, i.e.:
Now we can get:
instead of:
Well, have you learned it?
End of this section
Related Topics¶
- C++ Class Initializer List Idiom | C++那些事
- C++ Prefer Class Templates over Function Templates | C++那些事
- C++ copy-and-swap Idiom | C++那些事
- C++ Dependency Injection | C++那些事
- C++ enum class Strongly Typed Enums | C++那些事
- C++ Header-Only vs Implementation Separation | C++那些事
- C++ Lazy Evaluation Idiom | C++那些事
- C++ Lookup Table Idiom | C++那些事
- C++ Member Pointers and Type Information | C++那些事
- C++ Mixin Idiom | C++那些事