Compreendendo operator new e operator delete no C++

Um mergulho na implementação da STL revela a utilização do operator new. A análise de um fragmento de código template demonstra sua aplicação:

template<typename Type>
inline void release_memory(Type* ptr) {
    ::operator delete(ptr);
    // Note: operator delete também pode ser sobrecarregado
}

Isso levou a uma investigação mais aprofundada desses operadores. Uma dúvida inicial surgiu sobre a finalidade do operador de escopo global ::. O seguinte teste esclareceu sua função:

#include <iostream>

class Base {
public:
    void executar() {
        std::cout << "Base" << std::endl;
    }
};

void executar() {
    std::cout << "Global" << std::endl;
}

class Derivada : public Base {
public:
    void executar() {
        std::cout << "Derivada" << std::endl;
        ::executar(); // Chama explicitamente a função global
    }
};

int main() {
    Derivada* obj = new Derivada();
    obj->executar();
    delete obj;
    return 0;
}

O resultado da execução é:

Derivada
Global

Isso demonstra que :: é usado para desambiguar e acessar símbolos no escopo global, ignorando definições locais ou de classe com o mesmo nome.

A sobrecarga do operator new e operator delete permite o controle customizado sobre a alocação e liberação de memória. A busca por uma sobrecarga segue o princípio de cobertura de escopo: o compilador primeiro procura na classe, depois no escopo global. Se uma correspondência for encontrada no escopo da classe, a busca para, mesmo que um operador global compatível exista.

#include <iostream>
#include <new>
#include <vector>

class GerenciadorMemoria {
public:
    // Sobrecarga do new padrão
    void* operator new(std::size_t tamanho) {
        std::cout << "Gerenciador: alocando bytes: " << tamanho << "\n";
        void* ponteiro = ::operator new(tamanho);
        return ponteiro;
    }

    // Sobrecarga do new com nothrow
    void* operator new(std::size_t tamanho, const std::nothrow_t& tag) noexcept {
        std::cout << "Gerenciador: alocando (nothrow) bytes: " << tamanho << "\n";
        return ::operator new(tamanho, tag);
    }

    // Placement new - constrói no endereço fornecido
    void* operator new(std::size_t /*tamanho*/, void* endereco_existente) {
        std::cout << "Gerenciador: placement new\n";
        return endereco_existente;
    }

    // Sobrecarga do delete padrão
    void operator delete(void* ponteiro) {
        std::cout << "Gerenciador: liberando memoria\n";
        ::operator delete(ponteiro);
    }

    // Sobrecarga do array new
    void* operator new[](std::size_t tamanho) {
        std::cout << "Gerenciador: alocando array de bytes: " << tamanho << "\n";
        return ::operator new[](tamanho);
    }

    // Opcional: sobrecarga para array delete
    void operator delete[](void* ponteiro) {
        std::cout << "Gerenciador: liberando array de memoria\n";
        ::operator delete[](ponteiro);
    }

    // Destrutor para demonstração
    ~GerenciadorMemoria() {
        std::cout << "Gerenciador destruido\n";
    }
};

int main() {
    // 1. Usar o new sobrecarregado da classe
    GerenciadorMemoria* obj1 = new GerenciadorMemoria;
    delete obj1;
    std::cout << "---\n";

    // 2. Usar o placement new (construção em memória pré-alocada)
    char buffer[sizeof(GerenciadorMemoria)];
    GerenciadorMemoria* obj2 = new (buffer) GerenciadorMemoria;
    // Para placement new, o delete normal NÃO deve ser chamado.
    // Chamamos o destrutor explicitamente.
    obj2->~GerenciadorMemoria();
    std::cout << "---\n";

    // 3. Alocar um array
    GerenciadorMemoria* arr = new GerenciadorMemoria[3];
    delete[] arr;
    std::cout << "---\n";

    // 4. Forçar o uso do operador new global, ignorando a sobrecarga da classe
    GerenciadorMemoria* obj3 = ::new GerenciadorMemoria;
    ::delete obj3;

    return 0;
}

A saída deste programa ilustra as diferentes chamadas realizadas pelos operadores sobrecarregados. Conceitos cruciais incluem:

  • Cobertura de Escopo: Se uma classe define operator new(size_t), chamadas como new MinhaClasse a utilizam. Chamar new(std::nothrow) MinhaClasse exigirá uma sobrecarga correspondente na classe; caso contrário, um erro de compilação ocorrerá, pois a busca para na classe e não prossegue para o escopo global.
  • Placement new: Permite a construção de um objeto em um endereço de memória já alocado (ex.: uma pilha, um pool ou memória compartilhada). Seu operador de exclusão correspondente é frequentemente um no-op ou realiza apenas a destruição do objeto, não a desalocação.
  • Encadeamento com o Global: A maioria das sobrecargas delega a alocação ou desalocação real para as versões globais (::operator new, ::operator delete) após adicionar lógica de rastreamento, logging ou gerenciamento especializado.

Tags: C++ memory-management operator-overloading custom-allocator placement-new

Publicado em 6-9 08:04 por Thomas