Alocação sem construção

2024-06-11 computer blog

Meu amigo está mexendo com allocator e fez o seguinte teste, o que pode dar a ideia errada para o iniciante, porque testei aqui e deu tudo certo:

#include <iostream>

struct A {
    A() = default;
    uint64_t x;
};

int main()
{
    auto alloc = std::allocator<A>();
    A* p = alloc.allocate(2);
    p[1].x = 10;
}

Porém, ele não havia revelado que a “struct” que ele usou não era dessas sem complexidade alguma, mas a std::string, que depende da construção porque possui membros que controlam o estado do objeto. E aí sim apenas alocar o espaço na memória não é suficiente. É preciso construir o objeto chamando o construtor.

#include <iostream>
#include <string>

struct A {
    A() = default;
    uint64_t x;
    std::string y; // new member
};

int main()
{
    auto alloc = std::allocator<A>();
    A* p = alloc.allocate(2);
    p[1].y = "y"; // crash
}

Ao depurar a chamada ao operator = que chama por sua vez o método std::string::assign é possível ver que o objeto está com os membros size e capacity com lixo. O que faz sentido, já que o construtor dessa string nunca foi chamado.

- this  0x00000216888f5858 <Error reading characters of string.>
std::string *
   [size]  14829735431805717965  unsigned __int64
   [capacity]  14829735431805717965  unsigned __int64
+ [allocator]  allocator  std::_Compressed_pair<
std::allocator<char>,std::_String_val<std::_Simple_types<char>>,1>
   [0]  <Unable to read memory>  char

Para corrigir isso podemos usar o que C++ chama de placement new: uma construção de objeto em memória previamente disponível.

A* p = alloc.allocate(2);
new (p) A[2]; // remember to use new array operator!
[comment] [Se não instanciou o template não tem nada de errado]