Ir para o conteúdo

Última atualização:2026-10-12

C++ evitar o construtor padrão

Em C++, alguns contêineres, ao ajustar o tamanho com o método resize(), acabam chamando o construtor padrão, o que pode não ser o comportamento desejado. Este artigo apresenta como usar um alocador personalizado para controlar esse processo e evitar a chamada da construção padrão.

Exemplo 1:

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

Você sabe o que é impresso neste caso?

A resposta é:

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

Aqui é preciso entender alguns pontos:

Primeiro: por que os dados, após o resize, viraram 0 — o que aconteceu?

Segundo: se eu quiser manter os dados originais, como fazer? Ou seja, quero que a saída seja:

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

Terceiro: o que acontece se os membros do vector não tiverem construtor padrão?

Quarto: quais são as limitações do resize?

Pois é exatamente isso que esta seção vai estudar: como fazer o resize evitar a chamada à construção padrão.

Nota: o espaço é limitado; o código completo da versão simplificada desta seção está na comunidade.

1. Análise do resize

Para deixar o texto mais fluido, usei o exemplo abaixo: proíbo a construção de Foo e, em seguida, faço resize dele num vector.

class Foo {
 public:
  Foo() = delete;

 private:
  int a;
};

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

Neste haverá erro de compilação, e veremos uma pilha de mensagens de erro.

Primeiro problema: vê-se que a pilha de chamadas chegou a __uninitialized_default_n, que então verifica se o tipo atual é um tipo trivial — eis a primeira limitação do 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>::

Segundo problema: como proibimos a construção padrão, o alocador falha ao fazer placement new de um objeto padrão no endereço original, e aí vem o erro — eis a segunda limitação!

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)...);
      |       ^~~~~~~~~~~~~~~~~~~

Dito isso, podemos entrar no assunto: vê-se que, no resize, usa-se o alocador para alocar memória; quando o tamanho passado é maior que size (assumindo aqui que está dentro de capacity, ou seja, o endereço é válido), usa-se o alocador para fazer placement new de um objeto construído pelo construtor padrão.

A implementação do código-fonte do gcc pode ser mostrada aqui — simples, não é? É só chamar 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;
}

Certo, o princípio do resize ficou claro; voltemos à questão do início.

2. Evitar a chamada ao construtor padrão

Como evitar a chamada ao construtor padrão?

Na verdade, é bem simples: basta bloquear o construct padrão do gcc — e como bloqueá-lo?

Aqui entra o segundo parâmetro do vector: o alocador!

3. Alocador

Para resolver esse problema, podemos usar um alocador personalizado. Um alocador personalizado pode controlar o comportamento de construção dos objetos, permitindo-nos escolher se usamos ou não a construção padrão ao chamar resize. Segue uma implementação simples de alocador personalizado, apenas com o código essencial.

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
  }
};

Basta não fazer nada dentro de construct, e já podemos usá-lo, ou seja:

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

Assim, obtém-se:

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

em vez de:

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

Pronto, você aprendeu?

Fim desta seção

Tópicos relacionados

Comentários