Skip to content

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:

// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
vec.resize(5);
vec.resize(10);

Do you know what the output is at this point?

The answer is:

[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 0, 0, 0, 0, 0]

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:

[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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.

class Foo {
 public:
  Foo() = delete;

 private:
  int a;
};

std::vector<Foo> f;
f.resize(10);

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.:

std::vector<Foo, no_init_alloc<Foo>> std_vec;

Now we can get:

[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

instead of:

[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 0, 0, 0, 0, 0]

Well, have you learned it?

End of this section

Comments