Respostas:
Ele permite que você obtenha uma shared_ptr
instância válida para this
quando tudo o que você tem é this
. Sem ele, você não teria nenhuma maneira de obter um shared_ptr
para this
, a menos que você já teve um como um membro. Este exemplo da documentação do impulso para enable_shared_from_this :
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_from_this();
}
}
int main()
{
shared_ptr<Y> p(new Y);
shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p)); // p and q must share ownership
}
O método f()
retorna um válido shared_ptr
, mesmo que não tivesse instância de membro. Observe que você não pode simplesmente fazer isso:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
O ponteiro compartilhado que isso retornou terá uma contagem de referência diferente da "adequada" e uma delas acabará perdendo e mantendo uma referência pendente quando o objeto for excluído.
enable_shared_from_this
tornou-se parte do padrão C ++ 11. Você também pode obtê-lo de lá, bem como do impulso.
std::shared_ptr
construtor em um ponteiro bruto, se herdar de std::enable_shared_from_this
. Não sei se a semântica do Boost foi atualizada para suportar isso.
std::shared_ptr
para um objeto que já é gerenciado por outro std::shared_ptr
não consultará a referência fraca armazenada internamente e, portanto, levará a um comportamento indefinido". ( pt.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p
?
std::make_shared<T>
.
do artigo do Dr. Dobbs sobre indicadores fracos, acho que este exemplo é mais fácil de entender (fonte: http://drdobbs.com/cpp/184402026 ):
... código como este não funcionará corretamente:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Nenhum dos dois shared_ptr
objetos conhece o outro, portanto, ambos tentarão liberar o recurso quando forem destruídos. Isso geralmente leva a problemas.
Da mesma forma, se uma função membro precisar de um shared_ptr
objeto que possua o objeto em que está sendo chamada, ela não poderá simplesmente criar um objeto em tempo real:
struct S
{
shared_ptr<S> dangerous()
{
return shared_ptr<S>(this); // don't do this!
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->dangerous();
return 0;
}
Este código tem o mesmo problema que o exemplo anterior, embora de uma forma mais sutil. Quando é construído, o shared_pt
objeto r sp1
possui o recurso recém-alocado. O código dentro da função de membro S::dangerous
não sabe sobre esse shared_ptr
objeto, portanto, o shared_ptr
objeto que ele retorna é distinto sp1
. Copiar o novo shared_ptr
objeto para sp2
não ajuda; quando sp2
sai do escopo, ele libera o recurso e, quando sp1
sai do escopo, libera o recurso novamente.
A maneira de evitar esse problema é usar o modelo de classe enable_shared_from_this
. O modelo usa um argumento de tipo de modelo, que é o nome da classe que define o recurso gerenciado. Essa classe, por sua vez, deve ser derivada publicamente do modelo; como isso:
struct S : enable_shared_from_this<S>
{
shared_ptr<S> not_dangerous()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->not_dangerous();
return 0;
}
Ao fazer isso, lembre-se de que o objeto no qual você chama shared_from_this
deve pertencer a um shared_ptr
objeto. Isso não vai funcionar:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);
ser preferível usá-lo shared_ptr<S> sp1 = make_shared<S>();
, consulte por exemplo stackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();
porque a armadilha aqui é que você deve criar um shared_ptr da maneira normal antes de ligar shared_from_this()
pela primeira vez! É realmente fácil errar! Antes do C ++ 17, é UB chamar shared_from_this()
antes que exatamente um shared_ptr tenha sido criado da maneira normal: auto sptr = std::make_shared<S>();
ou shared_ptr<S> sptr(new S());
. Felizmente, a partir do C ++ 17, isso será lançado.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();
<- É permitido chamar shared_from_this apenas em um objeto compartilhado anteriormente, ou seja, em um objeto gerenciado por std :: shared_ptr <T>. Caso contrário, o comportamento é indefinido (até C ++ 17) std :: bad_weak_ptr é lançado (pelo construtor shared_ptr a partir de um fraco_estilo construído por padrão) (desde C ++ 17). . Portanto, a realidade é que deve ser chamada always_dangerous()
, porque você precisa saber se já foi compartilhado ou não.
Aqui está minha explicação, do ponto de vista de porcas e parafusos (a resposta principal não 'clicou' comigo). * Observe que esse é o resultado da investigação da origem do shared_ptr e do enable_shared_from_this fornecido com o Visual Studio 2012. Talvez outros compiladores implementem o enable_shared_from_this de maneira diferente ... *
enable_shared_from_this<T>
adiciona um privado weak_ptr<T>
instância à T
qual contém a ' única contagem de referência verdadeira ' para a instância de T
.
Então, quando você cria um shared_ptr<T>
para um novo T *, o ponto fraco_ptr interno do T * é inicializado com uma contagem de ref 1. O novo shared_ptr
basicamente se baseia nisso weak_ptr
.
T
Em seguida, em seus métodos, pode chamar shared_from_this
para obter uma instância shared_ptr<T>
disso para a mesma contagem de referência armazenada internamente . Dessa forma, você sempre tem um lugar onde T*
a contagem de ref é armazenada, em vez de ter várias shared_ptr
instâncias que não se conhecem e cada uma pensa que é a shared_ptr
responsável pela contagem de ref T
e excluí-la quando a ref -count chega a zero.
So, when you first create...
porque isso é um requisito (como você diz que o fraca_ptr não é inicializado até você passar o ponteiro de objetos para um compartilhado_ptrador!) E esse requisito é onde as coisas podem dar terrivelmente errado se você estiver descuidado. Se você não criar um shared_ptr antes de ligar, shared_from_this
receberá o UB - da mesma forma, se você criar mais de um shared_ptr, também receberá o UB. Você precisa, de alguma forma, criar um shared_ptr exatamente uma vez.
enable_shared_from_this
é frágil, pois o objetivo é obter um shared_ptr<T>
de a T*
, mas, na realidade, quando você recebe um ponteiro T* t
, geralmente não é seguro assumir que algo já está sendo compartilhado ou não, e fazer o palpite errado é UB.
Observe que o uso de um boost :: intrusive_ptr não sofre esse problema. Geralmente, é uma maneira mais conveniente de contornar esse problema.
enable_shared_from_this
permite que você trabalhe com uma API que aceite especificamente shared_ptr<>
. Na minha opinião, essa API geralmente está fazendo errado (como é melhor deixar que algo mais alto na pilha possua a memória), mas se você for forçado a trabalhar com essa API, essa é uma boa opção.
É exatamente o mesmo no c ++ 11 e posterior: é para permitir a capacidade de retornar this
como um ponteiro compartilhado, poisthis
fornece um ponteiro bruto.
em outras palavras, permite transformar código como este
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
nisso:
class Node : std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getParent const() {
std::shared_ptr<Node> parent = m_parent.lock();
if (parent) {
return parent;
} else {
return shared_from_this();
}
}
private:
std::weak_ptr<Node> m_parent;
};
shared_ptr
. Você pode alterar a interface para garantir que seja o caso.
std::shared_ptr<Node> getParent const()
, eu normalmente o exporia NodePtr getParent const()
. Se você absolutamente precisa acessar o ponteiro bruto interno (melhor exemplo: lidando com uma biblioteca C), é std::shared_ptr<T>::get
isso que eu odeio mencionar porque eu tenho esse acessador de ponteiro bruto usado muitas vezes pelo motivo errado.
Outra maneira é adicionar um weak_ptr<Y> m_stub
membro ao class Y
. Então escreva:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Útil quando você não pode alterar a classe da qual você está derivando (por exemplo, estendendo a biblioteca de outras pessoas). Não se esqueça de inicializar o membro, por exemplo,m_stub = shared_ptr<Y>(this)
, , é válido mesmo durante um construtor.
Tudo bem se houver mais stubs como este na hierarquia de herança, isso não impedirá a destruição do objeto.
Editar: Conforme indicado corretamente pelo usuário nobar, o código destruirá o objeto Y quando a atribuição for concluída e as variáveis temporárias forem destruídas. Portanto, minha resposta está incorreta.
shared_ptr<>
que não apague seu pontapé, isso é um exagero. Você pode simplesmente dizer return shared_ptr<Y>(this, no_op_deleter);
onde no_op_deleter
está um objeto de função unária que não aceita Y*
e não faz nada.
m_stub = shared_ptr<Y>(this)
irá construir e destruir imediatamente um shared_ptr temporário disso. Quando essa instrução terminar, this
será excluída e todas as referências subseqüentes ficarão pendentes.
enable_shared_from_this
, ela mantém um weak_ptr
de si (preenchido pelo ctor), retornado como shared_ptr
quando você chama shared_from_this
. Em outras palavras, você está duplicando o que enable_shared_from_this
já fornece.