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.
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_ptr
gerenciar os dados e fornecer std::weak_ptr
aos 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_ptr
sozinho, porque todas as std::shared_ptr
instâncias compartilham a propriedade dos dados que não são removidos antes de todas as instâncias std::shared_ptr
serem 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";
}
std::weak_ptr::lock
cria um novo std::shared_ptr
que compartilha a propriedade do objeto gerenciado.
Outra resposta, esperançosamente mais simples. (para outros googlers)
Suponha que você tenha Team
e Member
objetos.
Obviamente, é um relacionamento: o Team
objeto terá ponteiros para ele Members
. E é provável que os membros também tenham um ponteiro de volta para seu Team
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 usa shared_ptr
e o "proprietário" usa a weak_ptr
para seu pai e o converte temporariamente para shared_ptr
quando 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
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_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 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.
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, usando std::bind
capturas 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.
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 NULL shared_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.
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:
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());
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.
Além dos outros casos de uso válidos já mencionados, std::weak_ptr
é uma ferramenta incrível em um ambiente multithread, porque
std::shared_ptr
em conjunto com std::weak_ptr
é seguro contra ponteiros pendentes - em oposição a std::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.)
// 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_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 é usar std::shared_ptr
em 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.
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.
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.
weak_ptr
lidar com uma dependência circular sem alterar a lógica do programa como substituto shared_ptr
?" :-)
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.
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.