Minha classe herda de várias bases, uma das quais é std::enable_shared_from_this
. Deve ser a primeira base?
Suponha o seguinte código de exemplo:
struct A { ~A(); };
struct B { ~B(); };
struct C : A, B, std::enable_shared_from_this<C> {};
std::make_shared<C>();
Quando ~A()
e ~B()
executar, posso ter certeza de que o armazenamento onde C
morava ainda está presente?
std::enable_shared_from_this
não faz muito. Seu exemplo parece bom para mim (supondo que você não esteja tentando fazer algo inteligente~A
e~B
, comothis
C*
enable_shared_from_this
deve ser uma base inequívoca acessível. No meu exemplo, é.C
é uma estrutura. Herda publicamente.class
epublic
. Eu escolhistruct
o exemplo para evitar digitar.~weak_ptr();
Efeitos: destrói esteweak_ptr
objeto, mas não tem efeito no objeto para o qual o ponteiro armazenado aponta." Ênfase minha.shared_ptr
morre. Mesmo que issoweak_ptr
evite que o bloco de controle seja desalocado, acho que não importa.Respostas:
Claro! Seria difícil usar uma classe base que tenta liberar sua própria memória (a memória em que reside). Não tenho certeza se é formalmente legal.
As implementações não fazem isso: quando a
shared_ptr<T>
é destruída ou redefinida, a contagem de referência (RC) da propriedade compartilhada deT
é diminuída (atomicamente); se alcançou 0 no decremento, a destruição / exclusão deT
é iniciada.Então, a contagem de proprietários fracos-ou-T-existe é diminuída (atomicamente), como
T
não existe mais: precisamos saber se somos a última entidade interessada no bloco de controle; se o decréscimo der um resultado diferente de zero, significaweak_ptr
que existem alguns que compartilham (podem ser 1 compartilhamento ou 100%) de propriedade do bloco de controle e agora são responsáveis pela desalocação.De qualquer forma, em algum momento, o decremento atômico terminará com um valor zero, para o último coproprietário.
Aqui não há tópicos, não-determinismo e, obviamente, o último
weak_ptr<T>
foi destruído durante a destruição deC
. (A suposição não escrita em sua pergunta é que nenhum outroweak_ptr<T>
foi mantido.)A destruição sempre acontece nessa ordem exata . O bloco de controle é usado para destruição, pois não se
shared_ptr<T>
sabe (em geral) qual destruidor (potencialmente não virtual) da classe (potencialmente diferente) mais derivada a ser chamada . (O bloco de controle também sabe não desalocar a memória na contagem compartilhada atingindo zero paramake_shared
.)A única variação prática entre implementações parece ser sobre os detalhes finos de cercas de memória e evitar algumas operações atômicas em casos comuns.
fonte
weak_count
é 1 para um objeto que foimake_shared
eliminado, mesmo que não hajaweak_ptr
s. Liberando apenas osshared_ptr
primeiros decrementosuse_count
. Se ele se tornou 0, o objeto (mas não o bloco de controle) é destruído. Entãoweak_count
é decrementado e, se 0, o bloco de controle é destruído + liberado. Um objeto que herda deenable_shared_from_this
começa comweak_count
= 2. Uma solução brilhante por implementadores de STL, conforme o esperado.random_access_iterator_tag
). Existe um acordo informal para chamar qualquer coisa relacionada a contêineres como parte do STL. tl; dr: nem todos os modelos na lib std fazem parte do STL e nem todos os não modelos estão fora dele.Não, e a ordem das classes base é irrelevante. Até o uso (ou não) de enable_shared_from_this é irrelevante.
Quando um objeto C é destruído (não importa o que aconteça),
~C()
ele será chamado antes de ambos~A()
e~B()
, dessa maneira, os destruidores de base funcionam. Se você tentar "reconstruir" o objeto C no destruidor de base e acessar os campos nele, esses campos já foram destruídos, portanto, você terá um comportamento indefinido.fonte
enable_shared_from_this
pode aparecer em qualquer lugar da lista base, são necessárias implementações para liberar memória após a destruição de todo o objeto, independentemente de como ele seja herdado deenable_shared_from_this
" ou " deve ser a primeira base, herdando outro lugar como UB "ou" Esse comportamento não é especificado ou a qualidade da implementação ".Se você criar um objeto c do tipo C, com as bases A, B e um contador de referência através da herança da base
enable_shared_from_this<T>
, primeiro a memória será alocada para todo o objeto resultante, incluindo as bases em geral e a baseenable_shared_from_this<T>
. O objeto não será destruído até que o último proprietário (também conhecido como shared_ptr) renuncie à propriedade. Nesse momento ~ enable_shared ..., ~ B e ~ A serão executados após ~ C. A memória alocada completa ainda está garantida para estar lá até após a execução do último destruidor ~ A. Depois que ~ A é executado, a memória completa do objeto é liberada de uma só vez. Então, para responder sua pergunta:Sim, embora você não possa acessá-lo legalmente, mas por que você precisaria saber? Que problema você está tentando evitar?
fonte
shared_ptr
,weak_ptr
eenable_shared_from_this
são necessárias para manter a memória por tempo suficiente para tornar isso seguro, mesmo quando essaenable_shared_from_this
não é a primeira base.enable_shared_from_this
depois de outra classe base.