ponteiros inteligentes (impulso) explicados

220

Qual é a diferença entre o seguinte conjunto de ponteiros? Quando você usa cada ponteiro no código de produção?

Exemplos seriam apreciados!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

Você usa aumento no código de produção?

Nulo
fonte

Respostas:

339

Propriedades básicas de ponteiros inteligentes

É fácil quando você tem propriedades que podem ser atribuídas a cada ponteiro inteligente. Existem três propriedades importantes.

  • nenhuma propriedade
  • transferência de propriedade
  • quota de propriedade

O primeiro significa que um ponteiro inteligente não pode excluir o objeto, porque não o possui. O segundo significa que apenas um ponteiro inteligente pode apontar para o mesmo objeto ao mesmo tempo. Se o ponteiro inteligente deve ser retornado das funções, a propriedade é transferida para o ponteiro inteligente retornado, por exemplo.

O terceiro significa que vários ponteiros inteligentes podem apontar para o mesmo objeto ao mesmo tempo. Isso também se aplica a um ponteiro bruto , no entanto, os ponteiros brutos não possuem um recurso importante: eles não definem se são proprietários ou não. Um compartilhamento de ponteiro inteligente de propriedade excluirá o objeto se todos os proprietários desistirem do objeto. Esse comportamento é necessário com frequência, portanto, os ponteiros inteligentes proprietários compartilhados são amplamente difundidos.

Alguns proprietários de ponteiros inteligentes não suportam nem o segundo nem o terceiro. Portanto, eles não podem ser retornados de funções ou transmitidos para outro lugar. Qual é o mais adequado para os RAIIpropósitos em que o ponteiro inteligente é mantido local e é apenas criado para liberar um objeto depois que ele sai do escopo.

A participação da propriedade pode ser implementada com um construtor de cópias. Isso naturalmente copia um ponteiro inteligente e a cópia e o original fazem referência ao mesmo objeto. Atualmente, a transferência de propriedade não pode ser implementada no C ++, porque não há como transferir algo de um objeto para outro suportado pela linguagem: Se você tentar retornar um objeto de uma função, o que está acontecendo é que o objeto é copiado. Portanto, um ponteiro inteligente que implemente a transferência de propriedade deve usar o construtor de cópias para implementar essa transferência de propriedade. No entanto, isso, por sua vez, interrompe seu uso em contêineres, porque os requisitos indicam um certo comportamento do construtor de cópia dos elementos dos contêineres que é incompatível com o chamado comportamento de "construtor móvel" desses ponteiros inteligentes.

O C ++ 1x fornece suporte nativo para transferência de propriedade, introduzindo os chamados "mover construtores" e "mover operadores de atribuição". Ele também vem com um ponteiro inteligente de transferência de propriedade chamado unique_ptr.

Categorizando ponteiros inteligentes

scoped_ptré um ponteiro inteligente que não é transferível nem compartilhável. É apenas utilizável se você precisar alocar memória localmente, mas certifique-se de que ela será liberada novamente quando ficar fora do escopo. Mas ainda pode ser trocado por outro scoped_ptr, se você desejar fazer isso.

shared_ptré um ponteiro inteligente que compartilha a propriedade (terceiro tipo acima). Ele é contado como referência para ver quando a última cópia sai do escopo e, em seguida, libera o objeto gerenciado.

weak_ptré um ponteiro inteligente não proprietário. É usado para referenciar um objeto gerenciado (gerenciado por um shared_ptr) sem adicionar uma contagem de referência. Normalmente, você precisaria obter o ponteiro bruto do shared_ptr e copiá-lo. Mas isso não seria seguro, pois você não teria como verificar quando o objeto foi realmente excluído. Portanto, weak_ptr fornece meios referenciando um objeto gerenciado por shared_ptr. Se você precisar acessar o objeto, poderá bloquear o gerenciamento (para evitar que, em outro thread, um shared_ptr o libere enquanto você usa o objeto) e, em seguida, use-o. Se o fraca_ptr apontar para um objeto já excluído, ele notará você lançando uma exceção. Usar o fraca_ptr é mais benéfico quando você tem uma referência cíclica: A contagem de referência não pode lidar facilmente com essa situação.

intrusive_ptré como um shared_ptr, mas não mantém a contagem de referência em um shared_ptr, mas deixa de aumentar / diminuir a contagem para algumas funções auxiliares que precisam ser definidas pelo objeto gerenciado. Isso tem a vantagem de que um objeto já referenciado (que possui uma contagem de referência incrementada por um mecanismo externo de contagem de referência) pode ser inserido em um intrusive_ptr - porque a contagem de referência não é mais interna ao ponteiro inteligente, mas o ponteiro inteligente usa um existente mecanismo de contagem de referência.

unique_ptré um ponteiro de transferência de propriedade. Você não pode copiá-lo, mas pode movê-lo usando os construtores de movimento do C ++ 1x:

unique_ptr<type> p(new type);
unique_ptr<type> q(p); // not legal!
unique_ptr<type> r(move(p)); // legal. p is now empty, but r owns the object
unique_ptr<type> s(function_returning_a_unique_ptr()); // legal!

Essa é a semântica que o std :: auto_ptr obedece, mas, devido à falta de suporte nativo para movimentação, ele falha em fornecê-los sem armadilhas. unique_ptr roubará automaticamente recursos de um outro unique_ptr temporário, que é um dos principais recursos da semântica de movimentação. auto_ptr será descontinuado na próxima versão do C ++ Standard em favor de unique_ptr. O C ++ 1x também permitirá empacotar objetos que são apenas móveis, mas não podem ser copiados em contêineres. Assim, você pode inserir unique_ptr's em um vetor, por exemplo. Vou parar por aqui e fazer referência a um bom artigo sobre isso, se você quiser ler mais sobre isso.

Johannes Schaub - litb
fonte
3
obrigado pelo elogio cara. agradeço, assim você também receberá +1 agora: p
Johannes Schaub - litb 20/02
@ Litb: Eu tenho uma dúvida em "transferência de propriedade"; Concordo que não há transferência real de propriedade entre objetos no C ++ 03, mas para ponteiros inteligentes isso não pode ser feito, pelo mecanismo de cópia destrutiva indicado aqui informit.com/articles/article.aspx?p=31529&seqNum= 5 .
Legends2k 18/03/10
3
resposta fantástica. Nota: auto_ptrjá está obsoleto (C ++ 11).
Nicklay
2
"isso, por sua vez, interrompe seu uso em contêineres, porque os requisitos declaram um certo comportamento do construtor de cópia de elementos de contêineres que é incompatível com o chamado comportamento de" construtor móvel "desses ponteiros inteligentes". Não entendi essa parte.
quer
Também me disseram que isso intrusive_ptrpode ser preferível a shared_ptruma melhor coerência do cache. Aparentemente, o cache tem melhor desempenho se você armazenar a contagem de referência como parte da memória do próprio objeto gerenciado, em vez de um objeto separado. Isso pode ser implementado em um modelo ou superclasse do objeto gerenciado.
El13
91

scoped_ptr é o mais simples. Quando sai do escopo, é destruído. O código a seguir é ilegal (scoped_ptrs não é copiável), mas ilustra um ponto:

std::vector< scoped_ptr<T> > tPtrVec;
{
     scoped_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // raw T* is freed
}
tPtrVec[0]->DoSomething(); // accessing freed memory

shared_ptr é referência contada. Sempre que ocorre uma cópia ou atribuição, a contagem de referência é incrementada. Sempre que o destruidor de uma instância é acionado, a contagem de referência para o T * bruto é diminuída. Quando é 0, o ponteiro é liberado.

std::vector< shared_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     // This copy to tPtrVec.push_back and ultimately to the vector storage
     // causes the reference count to go from 1->2
     tPtrVec.push_back(tPtr);
     // num references to T goes from 2->1 on the destruction of tPtr
}
tPtrVec[0]->DoSomething(); // raw T* still exists, so this is safe

weak_ptr é uma referência fraca a um ponteiro compartilhado que exige que você verifique se o shared_ptr apontado ainda está por perto

std::vector< weak_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // num references to T goes from 1->0
}
shared_ptr<T> tPtrAccessed =  tPtrVec[0].lock();
if (tPtrAccessed[0].get() == 0)
{
     cout << "Raw T* was freed, can't access it"
}
else
{
     tPtrVec[0]->DoSomething(); // raw 
}

intrusive_ptr geralmente é usado quando há um smart ptr de terceiros que você deve usar. Ele chamará uma função livre para adicionar e diminuir a contagem de referência. Consulte o link para aumentar a documentação para obter mais informações.

Doug T.
fonte
não é if (tPtrAccessed[0].get() == 0)suposto ser if (tPtrAccessed.get() == 0) ?
Rajeshwar
@DougT. Você acredita que Java usa a mesma idéia com referências? Macio, duro, fraco etc?
gansub
20

Não negligencie boost::ptr_containerem nenhuma pesquisa sobre indicadores inteligentes de impulso. Eles podem ser inestimáveis ​​em situações em que um exemplo std::vector<boost::shared_ptr<T> >seria muito lento.

timday
fonte
Na verdade, da última vez que tentei, os testes comparativos mostraram que a diferença de desempenho havia diminuído significativamente desde que eu escrevi isso originalmente, pelo menos no PC típico HW! A abordagem mais eficiente do ptr_container ainda pode ter algumas vantagens em casos de uso de nicho.
timday 8/08/16
12

Eu segundo o conselho sobre como olhar para a documentação. Não é tão assustador quanto parece. E algumas dicas curtas:

  • scoped_ptr- um ponteiro excluído automaticamente quando sai do escopo. Nota - nenhuma atribuição é possível, mas não apresenta custos indiretos
  • intrusive_ptr- ponteiro de contagem de referência sem sobrecarga de smart_ptr. No entanto, o próprio objeto armazena a contagem de referência
  • weak_ptr- trabalha em conjunto shared_ptrpara lidar com as situações que resultam em dependências circulares (leia a documentação e pesquise no google uma bela imagem;)
  • shared_ptr - o genérico, mais poderoso (e pesado) dos indicadores inteligentes (daqueles oferecidos pelo impulso)
  • Também existe o antigo auto_ptr, que garante que o objeto para o qual ele aponta seja destruído automaticamente quando o controle sair de um escopo. No entanto, possui semântica de cópia diferente da dos demais.
  • unique_ptr- virá com C ++ 0x

Resposta à edição: Sim

Anônimo
fonte
8
Eu vim aqui porque achei a documentação do impulso muito assustadora.
Francois Botha