Comecei a estudar ponteiros inteligentes do C ++ 11 e não vejo nenhum uso útil de std::weak_ptr
. Alguém pode me dizer quando std::weak_ptr
é útil / necessário?
c++11
shared-ptr
weak-ptr
c++-faq
artm
fonte
fonte
Respostas:
Um bom exemplo seria um cache.
Para objetos acessados recentemente, você deseja mantê-los na memória, mantendo um ponteiro forte sobre eles. Periodicamente, você verifica o cache e decide quais objetos não foram acessados recentemente. Você não precisa manter aqueles na memória, para se livrar do ponteiro forte.
Mas e se esse objeto estiver em uso e algum outro código contiver um forte ponteiro para ele? Se o cache se livrar de seu único ponteiro para o objeto, ele nunca poderá encontrá-lo novamente. Portanto, o cache mantém um ponteiro fraco para os objetos que ele precisa encontrar se eles permanecerem na memória.
É exatamente isso que um ponteiro fraco faz - ele permite que você localize um objeto se ele ainda estiver por perto, mas não o manterá se nada mais precisar.
fonte
std::weak_ptr
é uma maneira muito boa de resolver o problema do ponteiro oscilante . Usando apenas ponteiros brutos, é impossível saber se os dados referenciados foram desalocados ou não. Em vez disso, ao permitirstd::shared_ptr
gerenciar os dados e fornecerstd::weak_ptr
aos usuários os dados, os usuários podem verificar a validade dos dados chamandoexpired()
oulock()
.Você não pode fazer isso
std::shared_ptr
sozinho, porque todas asstd::shared_ptr
instâncias compartilham a propriedade dos dados que não são removidos antes de todas as instânciasstd::shared_ptr
serem removidas. Aqui está um exemplo de como verificar o ponteiro oscilante usandolock()
:fonte
std::weak_ptr::lock
cria um novostd::shared_ptr
que compartilha a propriedade do objeto gerenciado.Outra resposta, esperançosamente mais simples. (para outros googlers)
Suponha que você tenha
Team
eMember
objetos.Obviamente, é um relacionamento: o
Team
objeto terá ponteiros para eleMembers
. E é provável que os membros também tenham um ponteiro de volta para seuTeam
objeto.Então você tem um ciclo de dependência. Se você usar
shared_ptr
, os objetos não serão mais liberados automaticamente quando você abandonar a referência sobre eles, porque eles se referem um ao outro de forma cíclica. Este é um vazamento de memória.Você quebra isso usando
weak_ptr
. O "proprietário" normalmente usashared_ptr
e o "proprietário" usa aweak_ptr
para seu pai e o converte temporariamente parashared_ptr
quando precisar acessar o pai.Armazene um ptr fraco:
depois use-o quando necessário
fonte
shared_ptr
é compartilhar a propriedade, para que ninguém tenha a responsabilidade específica de liberar a memória, ela é liberada automaticamente quando não é mais usada. A menos que haja um loop ... Você pode ter várias equipes compartilhando o mesmo jogador (equipes anteriores?). Se o objeto de equipe "possuir" os membros, não será necessário usar ashared_ptr
para começar.shared_ptr
referenciada por seus "membros da equipe", quando será destruída? O que você está descrevendo é um caso em que não há loop.Aqui está um exemplo, dado a mim por @jleahy: Suponha que você tenha uma coleção de tarefas, executadas de forma assíncrona e gerenciadas por um
std::shared_ptr<Task>
. Você pode fazer algo com essas tarefas periodicamente, para que um evento de timer possa atravessar aestd::vector<std::weak_ptr<Task>>
dar às tarefas algo a fazer. No entanto, simultaneamente, uma tarefa pode ter decidido simultaneamente que não é mais necessária e morre. O cronômetro pode, assim, verificar se a tarefa ainda está ativa, criando um ponteiro compartilhado a partir do ponteiro fraco e usando esse ponteiro compartilhado, desde que não seja nulo.fonte
Eles são úteis com o Boost.Asio quando você não tem garantia de que um objeto de destino ainda exista quando um manipulador assíncrono é chamado. O truque é vincular um
weak_ptr
ao objeto manipulador assíncrono, usandostd::bind
capturas ou lambda.Essa é uma variante do
self = shared_from_this()
idioma geralmente visto nos exemplos Boost.Asio, em que um manipulador assíncrono pendente não prolonga a vida útil do objeto de destino, mas ainda é seguro se o objeto de destino for excluído.fonte
this
self = shared_from_this()
idioma quando o manipulador chama métodos dentro da mesma classe.shared_ptr : mantém o objeto real.
weak_ptr : usa
lock
para se conectar ao proprietário real ou retorna um NULLshared_ptr
caso contrário.Grosso modo, o
weak_ptr
papel é semelhante ao papel da agência habitacional . Sem agentes, para alugar uma casa, talvez tenhamos que verificar casas aleatórias na cidade. Os agentes garantem que visitemos apenas as casas que ainda estão acessíveis e disponíveis para aluguel.fonte
weak_ptr
também é bom verificar a exclusão correta de um objeto - especialmente em testes de unidade. Os casos de uso típicos podem ter esta aparência:fonte
Ao usar ponteiros, é importante entender os diferentes tipos de ponteiros disponíveis e quando faz sentido usar cada um. Existem quatro tipos de ponteiros em duas categorias, da seguinte maneira:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
]
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
]
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
]
Os ponteiros brutos (às vezes chamados de "ponteiros herdados" ou "ponteiros C") fornecem um comportamento de ponteiro básico e são uma fonte comum de erros e vazamentos de memória. Os indicadores brutos não fornecem meios para controlar a propriedade do recurso e os desenvolvedores devem chamar 'delete' manualmente para garantir que não estejam criando um vazamento de memória. Isso se torna difícil se o recurso for compartilhado, pois pode ser um desafio saber se algum objeto ainda está apontando para o recurso. Por esses motivos, os ponteiros brutos geralmente devem ser evitados e usados apenas em seções críticas de desempenho do código com escopo limitado.
Ponteiros exclusivos são um ponteiro inteligente básico que 'possui' o ponteiro bruto subjacente ao recurso e é responsável por chamar excluir e liberar a memória alocada quando o objeto que 'possui' o ponteiro exclusivo fica fora do escopo. O nome 'exclusivo' refere-se ao fato de que apenas um objeto pode 'possuir' o ponteiro exclusivo em um determinado momento. A propriedade pode ser transferida para outro objeto através do comando move, mas um ponteiro exclusivo nunca pode ser copiado ou compartilhado. Por esses motivos, os ponteiros exclusivos são uma boa alternativa aos ponteiros brutos, no caso de apenas um objeto precisar do ponteiro em um determinado momento, e isso alivia o desenvolvedor da necessidade de liberar memória no final do ciclo de vida do objeto proprietário.
Ponteiros compartilhados são outro tipo de ponteiro inteligente que são semelhantes a ponteiros exclusivos, mas permitem que muitos objetos tenham propriedade sobre o ponteiro compartilhado. Como o ponteiro exclusivo, os ponteiros compartilhados são responsáveis por liberar a memória alocada quando todos os objetos terminarem apontando para o recurso. Isso é feito com uma técnica chamada contagem de referência. Cada vez que um novo objeto assume a propriedade do ponteiro compartilhado, a contagem de referência é incrementada em um. Da mesma forma, quando um objeto sai do escopo ou para de apontar para o recurso, a contagem de referência é decrementada em um. Quando a contagem de referência chega a zero, a memória alocada é liberada. Por esses motivos, os ponteiros compartilhados são um tipo muito poderoso de ponteiro inteligente que deve ser usado sempre que vários objetos precisarem apontar para o mesmo recurso.
Finalmente, ponteiros fracos são outro tipo de ponteiro inteligente que, em vez de apontar diretamente para um recurso, apontam para outro ponteiro (fraco ou compartilhado). Ponteiros fracos não podem acessar um objeto diretamente, mas podem saber se o objeto ainda existe ou se expirou. Um ponteiro fraco pode ser temporariamente convertido em um ponteiro compartilhado para acessar o objeto apontado (desde que ele ainda exista). Para ilustrar, considere o seguinte exemplo:
No exemplo, você tem um ponteiro fraco para a Reunião B. Você não é um "proprietário" na Reunião B para que possa terminar sem você e não sabe se terminou ou não, a menos que verifique. Se não terminou, você pode participar e participar, caso contrário, não poderá. Isso é diferente de ter um ponteiro compartilhado para a Reunião B, porque você seria um "proprietário" na Reunião A e na Reunião B (participando das duas ao mesmo tempo).
O exemplo ilustra como um ponteiro fraco funciona e é útil quando um objeto precisa ser um observador externo , mas não deseja a responsabilidade de compartilhar a propriedade. Isso é particularmente útil no cenário em que dois objetos precisam apontar um para o outro (também conhecido como referência circular). Com ponteiros compartilhados, nenhum objeto pode ser liberado porque ainda é 'fortemente' apontado pelo outro objeto. Quando um dos ponteiros é um ponteiro fraco, o objeto segurando o ponteiro fraco ainda pode acessar o outro objeto quando necessário, desde que ele ainda exista.
fonte
Além dos outros casos de uso válidos já mencionados,
std::weak_ptr
é uma ferramenta incrível em um ambiente multithread, porquestd::shared_ptr
em conjunto comstd::weak_ptr
é seguro contra ponteiros pendentes - em oposição astd::unique_ptr
em conjunto com ponteiros brutosstd::weak_ptr::lock()
é uma operação atômica (consulte também Sobre a segurança de encadeamento de weak_ptr )Considere uma tarefa para carregar todas as imagens de um diretório (~ 10.000) simultaneamente na memória (por exemplo, como um cache de miniaturas). Obviamente, a melhor maneira de fazer isso é um thread de controle, que manipula e gerencia as imagens, e vários threads de trabalho, que carregam as imagens. Agora, esta é uma tarefa fácil. Aqui está uma implementação muito simplificada (
join()
etc é omitida, os threads teriam que ser tratados de maneira diferente em uma implementação real, etc.)Mas isso se torna muito mais complicado, se você deseja interromper o carregamento das imagens, por exemplo, porque o usuário escolheu um diretório diferente. Ou mesmo se você quiser destruir o gerente.
Você precisaria de comunicação de encadeamento e teria que parar todos os encadeamentos do carregador antes de poder alterar seu
m_imageDatas
campo. Caso contrário, os carregadores continuariam carregando até que todas as imagens sejam concluídas - mesmo que já estejam obsoletas. No exemplo simplificado, isso não seria muito difícil, mas em um ambiente real as coisas podem ser muito mais complicadas.Os encadeamentos provavelmente seriam parte de um conjunto de encadeamentos usado por vários gerenciadores, dos quais alguns estão sendo interrompidos e outros não, etc. O parâmetro simples
imagesToLoad
seria uma fila bloqueada, na qual esses gerentes enviam suas solicitações de imagem de diferentes encadeamentos de controle com os leitores apresentando os pedidos - em uma ordem arbitrária - do outro lado. E assim a comunicação se torna difícil, lenta e propensa a erros. Uma maneira muito elegante de evitar qualquer comunicação adicional nesses casos é usarstd::shared_ptr
em conjunto comstd::weak_ptr
.Essa implementação é quase tão fácil quanto a primeira, não precisa de nenhuma comunicação de encadeamento adicional e pode fazer parte de um pool / fila de encadeamentos em uma implementação real. Como as imagens expiradas são ignoradas e as imagens não expiradas são processadas, os encadeamentos nunca precisariam ser interrompidos durante a operação normal. Você sempre pode mudar o caminho com segurança ou destruir seus gerentes, pois o leitor fn verifica se o ponteiro proprietário não está vencido.
fonte
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptr é um ponteiro inteligente que contém uma referência não-proprietária ("fraca") a um objeto gerenciado por std :: shared_ptr. Ele deve ser convertido em std :: shared_ptr para acessar o objeto referenciado.
std :: weak_ptr modela a propriedade temporária: quando um objeto precisa ser acessado apenas se existir, e pode ser excluído a qualquer momento por outra pessoa, std :: weak_ptr é usado para rastrear o objeto e é convertido em std: : shared_ptr para assumir propriedade temporária. Se o std :: shared_ptr original for destruído nesse momento, a vida útil do objeto será estendida até que o std :: shared_ptr temporário também seja destruído.
Além disso, std :: weak_ptr é usado para quebrar referências circulares de std :: shared_ptr.
fonte
Há uma desvantagem do ponteiro compartilhado: shared_pointer não pode lidar com a dependência do ciclo pai-filho. Significa se a classe pai usa o objeto da classe filho usando um ponteiro compartilhado, no mesmo arquivo se a classe filho usa o objeto da classe pai. O ponteiro compartilhado não conseguirá destruir todos os objetos, mesmo o ponteiro compartilhado não está chamando o destruidor no cenário de dependência de ciclo. o ponteiro basicamente compartilhado não suporta o mecanismo de contagem de referência.
Essa desvantagem é possível superar usando o fraco_pointer.
fonte
weak_ptr
lidar com uma dependência circular sem alterar a lógica do programa como substitutoshared_ptr
?" :-)Quando não queremos possuir o objeto:
Ex:
Na classe acima, o wPtr1 não possui o recurso apontado pelo wPtr1. Se o recurso for excluído, o wPtr1 expirará.
Para evitar dependência circular:
Agora, se fizermos o shared_ptr da classe B e A, o use_count dos dois ponteiros será dois.
Quando o shared_ptr sai do escopo, a contagem ainda permanece 1 e, portanto, o objeto A e B não é excluído.
resultado:
Como podemos ver pela saída, os ponteiros A e B nunca são excluídos e, portanto, vazam memória.
Para evitar esse problema, use fraca_ptr na classe A em vez de shared_ptr, o que faz mais sentido.
fonte
Eu vejo
std::weak_ptr<T>
como um identificador parastd::shared_ptr<T>
: Ele permite que eu obtenha ostd::shared_ptr<T>
se ele ainda existe, mas não prolongará sua vida útil. Existem vários cenários em que esse ponto de vista é útil:Outro cenário importante é interromper os ciclos nas estruturas de dados.
Herb Sutter tem uma excelente palestra que explica o melhor uso dos recursos da linguagem (neste caso, indicadores inteligentes) para garantir a Leak Freedom por padrão (ou seja: tudo se encaixa no local da construção; você dificilmente pode estragar tudo). É um deve assistir.
fonte