A classe std::enable_shared_from_this é um componente essencial em C++ para gerenciar ponteiros inteligentes, especialmente quando um objeto precisa obter um shared_ptr para si mesmo. Internamente, ela mantém um weak_ptr que é associado ao shared_ptr que gerencia o objeto.
A implementação típica, como vista em compiladores como MSVC, revela a estrutura:
template <class _Ty>
class enable_shared_from_this {
public:
using _Esft_type = enable_shared_from_this;
// Retorna um shared_ptr para o objeto atual.
_NODISCARD std::shared_ptr<_Ty> shared_from_this() {
return std::shared_ptr<_Ty>(_Wptr);
}
// Retorna um shared_ptr para o objeto atual (versão const).
_NODISCARD std::shared_ptr<const _Ty> shared_from_this() const {
return std::shared_ptr<const _Ty>(_Wptr);
}
// Retorna um weak_ptr para o objeto atual.
_NODISCARD std::weak_ptr<_Ty> weak_from_this() noexcept {
return _Wptr;
}
// Retorna um weak_ptr para o objeto atual (versão const).
_NODISCARD std::weak_ptr<const _Ty> weak_from_this() const noexcept {
return _Wptr;
}
protected:
// Construtor padrão.
constexpr enable_shared_from_this() noexcept : _Wptr() {}
// Construtor de cópia.
enable_shared_from_this(const enable_shared_from_this&) noexcept : _Wptr() {}
// Operador de atribuição (não altera _Wptr).
enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept {
return *this;
}
// Destrutor.
~enable_shared_from_this() = default;
private:
// Permite que shared_ptr acesse o membro _Wptr.
template <class _Yty>
friend class std::shared_ptr;
// O weak_ptr que gerencia a vida útil do objeto.
mutable std::weak_ptr<_Ty> _Wptr;
};
Cenários de Uso
A principal utilidade de enable_shared_from_this é permitir que um objeto, quando passado como parâmetro para outras funções a partir de seus próprios métodos, seja gerenciado por um shared_ptr. Isso evita problemas de duplo desligamento (double free) ou acesso a memória após a liberação. Ao invocar shared_from_this(), um novo shared_ptr é criado, compartilhando a propriedade do objeto. O uso de weak_ptr internamente previne ciclos de referência que poderiam impedir a liberação correta da memória.
Como Utilizar
Para empregar enable_shared_from_this, sua classe deve herdar publicamente dela. Dentro de um método de instância, você pode chamar shared_from_this() para obter um shared_ptr para o objeto atual e passá-lo adiante.
#include <iostream>
#include <memory>
class Item;
// Função que recebe um shared_ptr para um Item
void processItem(const std::shared_ptr<Item>& itemPtr) {
std::cout << "Processando item. Contagem de referências: " << itemPtr.use_count() << std::endl;
}
class Item : public std::enable_shared_from_this<Item> {
public:
void dispatch() {
auto selfPtr = shared_from_this(); // Obtém um shared_ptr para este objeto
std::cout << "Antes de chamar processItem. Contagem de referências: " << selfPtr.use_count() << std::endl;
processItem(selfPtr);
std::cout << "Depois de chamar processItem. Contagem de referências: " << selfPtr.use_count() << std::endl;
}
};
int main() {
std::shared_ptr<Item> item(new Item); // Cria um Item gerenciado por shared_ptr
item->dispatch();
std::cout << "Fim de main. Contagem de referências: " << item.use_count() << std::endl;
return 0;
}
A saída esperada demonstra o aumento e a subsequente diminuição da contagem de referências:
Antes de chamar processItem. Contagem de referências: 2
Processando item. Contagem de referências: 2
Depois de chamar processItem. Contagem de referências: 2
Fim de main. Contagem de referências: 1
Armadilhas e Considerações
Passando um Ponteiro Bruto this
Tentar criar um shared_ptr a partir de um ponteiro this bruto sem usar enable_shared_from_this é perigoso e leva a erros:
#include <iostream>
#include <memory>
class DataHolder;
void consumeData(const std::shared_ptr<DataHolder>& dh) {
std::cout << "Consumindo dados. Contagem: " << dh.use_count() << std::endl;
}
class DataHolder {
public:
void sendRaw() {
// ERRO: Cria um shared_ptr temporário que gerencia independentemente a memória.
consumeData(std::shared_ptr<DataHolder>(this));
}
};
int main() {
std::shared_ptr<DataHolder> data(new DataHolder);
data->sendRaw();
std::cout << "Fim de main. Contagem: " << data.use_count() << std::endl;
return 0;
}
Neste cenário, a criação de std::shared_ptr<DataHolder>(this) gera um novo gerenciador de ponteiro. Quando este ponteiro temporário é destruído, ele libera a memória, mesmo que o shared_ptr original em main ainda exista. A tentativa subsequente de liberar a memória em main resulta em um duplo desligamento e falha.
Da mesma forma, passar um ponteiro bruto this para uma função que pode liberá-lo (por exemplo, delete this;) é igualmente arriscado se o objeto origianl ainda estiver sendo gerenciado por um shared_ptr.
enable_shared_from_this e Objetos na Pilha
A funcionalidade de enable_shared_from_this está intrinsecamente ligada ao gerenciamento por shared_ptr. Objetos instanciados diretamente na pilha não podem usar shared_from_this() para obter um shared_ptr válido:
#include <memory>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void attemptShared() {
auto ptr = shared_from_this(); // Falhará se MyClass não for gerenciado por shared_ptr
}
};
int main() {
MyClass stackObj;
// stackObj.attemptShared(); // Isso causará um erro em tempo de execução ou comportamento indefinido.
return 0;
}
O motivo é que o _Wptr interno só é inicializado corretamente quando o objeto é criado através de um shared_ptr associado a um make_shared ou std::shared_ptr(new T).