Quando std :: weak_ptr é útil?

Respostas:

231

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.

David Schwartz
fonte
8
Então std :: wake_ptr pode apontar apenas para onde outro ponteiro aponta e aponta para nullptr quando o objeto apontado é excluído / não mais apontado por outros ponteiros?
27
@ RM: Basicamente, sim. Quando você tem um ponteiro fraco, pode tentar promovê-lo a um ponteiro forte. Se esse objeto ainda existir (porque pelo menos um ponteiro forte ainda existe), essa operação será bem-sucedida e fornecerá um ponteiro forte para ele. Se esse objeto não existir (porque todos os ponteiros fortes foram embora), essa operação falhará (e geralmente você reage jogando fora o ponteiro fraco).
David Schwartz
12
Enquanto um ponteiro forte mantém um objeto vivo, um fraco_ptr pode olhar para ele ... sem mexer com o tempo de vida do objeto.
The Vivandiere
3
Outro exemplo, que já usei algumas vezes, é na implementação de observadores; às vezes, torna-se conveniente que o assunto mantenha uma lista de indicadores fracos e faça sua própria limpeza de lista. Isso poupa um pouco de esforço para remover explicitamente os observadores quando eles são excluídos e, mais significativamente, você não precisa ter informações sobre os assuntos disponíveis ao destruir os observadores, o que geralmente simplifica bastante as coisas.
Jason C
3
Espere, o que há de errado com o cache mantendo um shared_ptr e apenas removendo-o da lista quando deve ser limpo da memória? Todos os usuários manterão um shared_ptr da mesma forma e o recurso em cache será limpo assim que todos os usuários tiverem terminado.
rubenvb
299

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 permitir std::shared_ptrgerenciar os dados e fornecer std::weak_ptraos usuários os dados, os usuários podem verificar a validade dos dados chamando expired()ou lock().

Você não pode fazer isso std::shared_ptrsozinho, porque todas as std::shared_ptrinstâncias compartilham a propriedade dos dados que não são removidos antes de todas as instâncias std::shared_ptrserem removidas. Aqui está um exemplo de como verificar o ponteiro oscilante usando lock():

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}
sunefred
fonte
1
Ok, é como se você localmente definisse um ponteiro (proprietário) como nulo (excluir memória), todos os outros ponteiros (fracos) para a mesma memória também sejam definidos como nulos
Pat-Laugh
std::weak_ptr::lockcria um novo std::shared_ptrque compartilha a propriedade do objeto gerenciado.
Sahib Yar
129

Outra resposta, esperançosamente mais simples. (para outros googlers)

Suponha que você tenha Teame Memberobjetos.

Obviamente, é um relacionamento: o Teamobjeto terá ponteiros para ele Members. E é provável que os membros também tenham um ponteiro de volta para seu Teamobjeto.

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 usa shared_ptre o "proprietário" usa a weak_ptrpara seu pai e o converte temporariamente para shared_ptrquando precisar acessar o pai.

Armazene um ptr fraco:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

depois use-o quando necessário

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope
Offirmo
fonte
1
Como isso é um vazamento de memória? Se a equipe for destruída, ela destruirá seus membros, portanto, a contagem de ref shared_ptr será 0 e também será destruída?
paulm
4
A equipe @paulm não destruirá os membros "its". O objetivo principal 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 a shared_ptrpara começar.
Offirmo
1
Ele não os destruirá, mas seu shared_ptr ficará fora do escopo com ele, diminui o use_count, portanto, neste momento use_count é 0 e, portanto, o shared_ptr excluirá o que está apontando?
paulm
2
@paulm Você está certo. Mas como, neste exemplo, a equipe também é shared_ptrreferenciada por seus "membros da equipe", quando será destruída? O que você está descrevendo é um caso em que não há loop.
Offirmo
14
Não é tão ruim, eu acho. Se um membro puder pertencer a muitas equipes, o uso de uma referência não funcionará.
Mazyod 9/08/2015
22

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 ae std::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.

Kerrek SB
fonte
4
: Parece um bom exemplo, mas você pode elaborar um exemplo um pouco mais? Estou pensando que quando uma tarefa é concluída, ela já deve ter sido removida do std :: vector <std :: weak_ptr <Task>> sem uma verificação periódica. Portanto, não tenho certeza se o std :: vector <std :: weak_ptr <>> é muito útil aqui.
Gob00st
Comentário semelhante com filas: digamos que você tenha objetos e os enfileire para algum recurso, os objetos podem ser excluídos enquanto aguarda. Portanto, se você enfileirar fraco_ptrs, não precisará se preocupar em excluir entradas da fila de lá. Weak_ptrs será invalidado e, em seguida, descartado quando encoutnered.
Zzz777
1
@ zzz777: A lógica que invalida os objetos pode nem estar ciente da existência da fila ou do vetor de observadores. Assim, os executa observadores um loop separado ao longo dos ponteiros fracos, agindo sobre os que ainda estão vivos, e remover os mortos do recipiente ...
Kerrek SB
1
@KerekSB: sim, e no caso de fila, você nem precisa fazer um loop separado - então, o recurso está disponível, você descarta fraca_ptrs expiradas (se houver) até obter um válido (se houver).
Zzz777
Você também pode remover os encadeamentos da coleção, mas isso criaria uma dependência e exigiria o bloqueio.
curiousguy
16

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_ptrao objeto manipulador assíncrono, usando std::bindcapturas ou lambda.

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

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.

Emile Cormier
fonte
Por que demorou tanto tempo para encontrar essa resposta ... PS você não está usando o seu captura dethis
Orwellophile
@Orwellophile fixed. Força do hábito ao usar o self = shared_from_this()idioma quando o manipulador chama métodos dentro da mesma classe.
Emile Cormier
16

shared_ptr : mantém o objeto real.

weak_ptr : usa lockpara se conectar ao proprietário real ou retorna um NULL shared_ptrcaso contrário.

ptr fraco

Grosso modo, o weak_ptrpapel é 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.

Saurav Sahu
fonte
14

weak_ptrtambé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:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
Biscoito
fonte
13

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:

  • Ponteiros brutos:
    • Ponteiro bruto [ie SomeClass* ptrToSomeClass = new SomeClass();]
  • Ponteiros inteligentes:
    • Ponteiros exclusivos [ie
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Ponteiros compartilhados [ie
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Ponteiros fracos [ie
      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:

  • Você está ocupado e tem reuniões sobrepostas: Reunião A e Reunião B
  • Você decide ir para a Reunião A e seu colega de trabalho para a Reunião B
  • Você diz ao seu colega de trabalho que, se a Reunião B continuar após o término da Reunião A, você ingressará
  • Os dois cenários a seguir podem ocorrer:
    • A reunião A termina e a reunião B ainda está em andamento.
    • A reunião A termina e a reunião B também termina, portanto você não pode participar

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.

Jeremy
fonte
6

Além dos outros casos de uso válidos já mencionados, std::weak_ptré uma ferramenta incrível em um ambiente multithread, porque

  • Ele não possui o objeto e, portanto, não pode impedir a exclusão de um thread diferente
  • std::shared_ptrem conjunto com std::weak_ptré seguro contra ponteiros pendentes - em oposição a std::unique_ptrem conjunto com ponteiros brutos
  • std::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.)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

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_imageDatascampo. 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 imagesToLoadseria 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 é usar std::shared_ptrem conjunto com std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

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.

user2328447
fonte
2

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.

MYLOGOS
fonte
" para quebrar referências circulares " como?
precisa
2

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.

ashutosh
fonte
Como uma referência fraca pode lidar com uma dependência circular?
curiousguy
1
@curiousguy, uma criança emprega uma referência fraca ao pai, então o pai pode ser desalocado quando não houver referências compartilhadas (fortes) apontando para ele. Assim, ao acessar o pai via filho, a referência fraca deve ser testada para verificar se o pai ainda está disponível. Como alternativa, para evitar essa condição extra, um mecanismo de rastreamento de referência circular (varredura de marca ou sondagem em decrementos de refcount, ambos com desempenho assintótico ruim) pode interromper as referências compartilhadas circulares quando as únicas referências compartilhadas para o pai e o filho pertencerem a cada um. de outros.
Shelby Moore III
@ShelbyMooreIII " precisa testar para ver se o pai ainda está disponível " sim, e você deve poder reagir corretamente ao caso indisponível! O que não ocorre com uma referência real (ou seja, forte). O que significa que ref fraco não é uma queda na substituição: requer uma mudança na lógica.
precisa saber é o seguinte
2
@curiousguy, você não perguntou: "Como é possível weak_ptrlidar com uma dependência circular sem alterar a lógica do programa como substituto shared_ptr?" :-)
Shelby Moore III
2

Quando não queremos possuir o objeto:

Ex:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

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:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

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.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

resultado:

A()
B()

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.

Swapnil
fonte
2

Eu vejo std::weak_ptr<T>como um identificador para std::shared_ptr<T>: Ele permite que eu obtenha o std::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:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

Outro cenário importante é interromper os ciclos nas estruturas de dados.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

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.

Escualo
fonte