Ponteiro inteligente - Smart pointer

Na ciência da computação , um ponteiro inteligente é um tipo de dado abstrato que simula um ponteiro enquanto fornece recursos adicionais, como gerenciamento automático de memória ou verificação de limites . Esses recursos têm como objetivo reduzir os bugs causados ​​pelo uso indevido de ponteiros, mantendo a eficiência. Os ponteiros inteligentes normalmente rastreiam a memória para a qual apontam e também podem ser usados ​​para gerenciar outros recursos, como conexões de rede e identificadores de arquivo. Os ponteiros inteligentes foram popularizados pela primeira vez na linguagem de programação C ++ durante a primeira metade da década de 1990 como refutação às críticas à falta de coleta de lixo automática do C ++ .

O uso incorreto do ponteiro pode ser uma fonte importante de bugs. Os ponteiros inteligentes evitam a maioria das situações de vazamentos de memória , tornando a desalocação de memória automática. De forma mais geral, eles tornam a destruição de objetos automática: um objeto controlado por um ponteiro inteligente é automaticamente destruído ( finalizado e, em seguida, desalocado) quando o último (ou único) proprietário de um objeto é destruído, por exemplo, porque o proprietário é uma variável local, e a execução sai do escopo da variável . Os ponteiros inteligentes também eliminam os ponteiros pendentes , adiando a destruição até que um objeto não esteja mais em uso.

Se uma linguagem oferece suporte à coleta de lixo automática (por exemplo, Java ou C # ), ponteiros inteligentes são desnecessários para os aspectos de recuperação e segurança do gerenciamento de memória, mas são úteis para outros fins, como gerenciamento de residência de estrutura de dados de cache e gerenciamento de recursos de objetos como identificadores de arquivo ou soquetes de rede .

Existem vários tipos de ponteiros inteligentes. Alguns trabalham com contagem de referência , outros atribuindo a propriedade de um objeto a um ponteiro.

História

Embora o C ++ tenha popularizado o conceito de ponteiros inteligentes, especialmente a variedade de contagem de referência , o predecessor imediato de uma das linguagens que inspiraram o design do C ++ tinha referências de contagem de referência incorporadas à linguagem. C ++ foi inspirado em parte por Simula67. Antepassado do Simula67 foi Simula I. Na medida em que de Simula I elemento é análogo ao C ++ 's ponteiro sem nula , e na medida em processo de Simula I com um manequim-declaração como seu corpo actividade é análogo ao C ++ da estrutura (que por si só é análogo ao do CAR Hoare ficha em seguida -trabalho contemporâneo dos anos 1960), Simula I tinha elementos contados de referência (isto é, expressões de ponteiro que abrigam a indireção) para processos (isto é, registros) até setembro de 1965, conforme mostrado nos parágrafos citados abaixo.

Os processos podem ser referenciados individualmente. Fisicamente, uma referência de processo é um ponteiro para uma área da memória que contém os dados locais do processo e algumas informações adicionais que definem seu estado atual de execução. No entanto, pelos motivos indicados na Seção 2.2, as referências de processo são sempre indiretas, por meio de itens chamados de elementos. Formalmente, uma referência a um processo é o valor de uma expressão do elemento de tipo .

… Valores de

elemento podem ser armazenados e recuperados por atribuições e referências a variáveis ​​de elemento e por outros meios.

A linguagem contém um mecanismo para tornar os atributos de um processo acessíveis de fora, ou seja, de dentro de outros processos. Isso é chamado de acesso remoto. Um processo é, portanto, uma estrutura de dados referenciável.

É importante notar a semelhança entre um processo cujo corpo de atividade é uma declaração fictícia, e o conceito de registro recentemente proposto por CAR Hoare e N. Wirth

Como C ++ emprestou a abordagem de Simula para alocação de memória - a nova palavra - chave ao alocar um processo / registro para obter um novo elemento para esse processo / registro - não é surpreendente que C ++ eventualmente ressuscitou o mecanismo de ponteiro inteligente contado por referência de Simula dentro do elemento como Nós vamos.

Recursos

Em C ++ , um ponteiro inteligente é implementado como uma classe de modelo que imita, por meio de sobrecarga de operador , os comportamentos de um ponteiro tradicional (bruto) (por exemplo, desreferenciamento, atribuição) enquanto fornece recursos adicionais de gerenciamento de memória.

Os ponteiros inteligentes podem facilitar a programação intencional , expressando, no tipo, como a memória do referente do ponteiro será gerenciada. Por exemplo, se uma função C ++ retorna um ponteiro, não há como saber se o chamador deve excluir a memória do referente quando o chamador terminar de fornecer as informações.

SomeType* AmbiguousFunction();  // What should be done with the result?

Tradicionalmente, as convenções de nomenclatura têm sido usadas para resolver a ambigüidade, que é uma abordagem propensa a erros e trabalhosa. C ++ 11 introduziu uma maneira de garantir o gerenciamento correto da memória, neste caso, declarando a função para retornar um unique_ptr,

std::unique_ptr<SomeType> ObviousFunction();

A declaração do tipo de retorno da função como um unique_ptrtorna explícito o fato de que o chamador assume a propriedade do resultado, e o tempo de execução C ++ garante que a memória será recuperada automaticamente. Antes do C ++ 11 , unique_ptr pode ser substituído por auto_ptr .

Criação de novos objetos

Para facilitar a alocação de um

std::shared_ptr<SomeType>

C ++ 11 introduzido:

auto s = std::make_shared<SomeType>(constructor, parameters, here);

e da mesma forma

std::unique_ptr<some_type>

Desde C ++ 14, pode-se usar:

auto u = std::make_unique<SomeType>(constructor, parameters, here);

É preferível, em quase todas as circunstâncias, usar esses recursos em vez da newpalavra - chave.

unique_ptr

C ++ 11 apresenta std::unique_ptr, definido no cabeçalho <memory>.

A unique_ptré um contêiner para um ponteiro bruto, que unique_ptrse diz ser o proprietário. A unique_ptrimpede explicitamente a cópia de seu ponteiro contido (como aconteceria com a atribuição normal), mas a std::movefunção pode ser usada para transferir a propriedade do ponteiro contido para outro unique_ptr. A unique_ptrnão pode ser copiado porque seu construtor de cópia e operadores de atribuição são explicitamente excluídos.

std::unique_ptr<int> p1(new int(5));
std::unique_ptr<int> p2 = p1;  // Compile error.
std::unique_ptr<int> p3 = std::move(p1);  // Transfers ownership. p3 now owns the memory and p1 is set to nullptr.

p3.reset();  // Deletes the memory.
p1.reset();  // Does nothing.

std::auto_ptrfoi descontinuado no C ++ 11 e completamente removido do C ++ 17 . O construtor de cópia e os operadores de atribuição de auto_ptrnão copiam realmente o ponteiro armazenado. Em vez disso, eles o transferem , deixando o auto_ptrobjeto anterior vazio. Essa era uma maneira de implementar propriedade restrita, de modo que apenas um auto_ptrobjeto pudesse possuir o ponteiro em um determinado momento. Isso significa que auto_ptrnão deve ser usado onde a semântica de cópia é necessária. Como auto_ptrjá existia com sua semântica de cópia, ele não poderia ser atualizado para ser um ponteiro apenas de movimentação sem quebrar a compatibilidade com versões anteriores com o código existente.

shared_ptr e weak_ptr

C ++ 11 apresenta std::shared_ptre std::weak_ptr, definido no cabeçalho <memory>. C ++ 11 também introduz std::make_shared( std::make_uniquefoi introduzido em C ++ 14) a alocação segura de memória dinâmica no paradigma RAII .

A shared_ptré um contêiner para um ponteiro bruto . Ele mantém a propriedade de contagem de referência de seu indicador contido em cooperação com todas as cópias do shared_ptr. Um objeto referenciado pelo ponteiro bruto contido será destruído quando e somente quando todas as cópias do shared_ptrforem destruídas.

std::shared_ptr<int> p0(new int(5));  // Valid, allocates 1 integer and initialize it with value 5.
std::shared_ptr<int[]> p1(new int[5]);  // Valid, allocates 5 integers.
std::shared_ptr<int[]> p2 = p1;  // Both now own the memory.

p1.reset();  // Memory still exists, due to p2.
p2.reset();  // Frees the memory, since no one else owns the memory.

A weak_ptré um contêiner para um ponteiro bruto. Ele é criado como uma cópia de um shared_ptr. A existência ou destruição de weak_ptrcópias de um shared_ptrnão tem efeito sobre o shared_ptrou suas outras cópias. Depois que todas as cópias de a shared_ptrforem destruídas, todas as weak_ptrcópias ficam vazias.

std::shared_ptr<int> p1 = std::make_shared<int>(5);
std::weak_ptr<int> wp1 {p1};  // p1 owns the memory.

{
  std::shared_ptr<int> p2 = wp1.lock();  // Now p1 and p2 own the memory.
  // p2 is initialized from a weak pointer, so you have to check if the
  // memory still exists!
  if (p2) {
    DoSomethingWith(p2);
  }
}
// p2 is destroyed. Memory is owned by p1.

p1.reset();  // Free the memory.

std::shared_ptr<int> p3 = wp1.lock(); 
// Memory is gone, so we get an empty shared_ptr.
if (p3) {  // code will not execute
  ActionThatNeedsALivePointer(p3);
}

Devido à implementação da contagem de referências de shared_ptruso , as referências circulares são potencialmente um problema. Uma cadeia circular pode ser quebrada alterando o código para que uma das referências seja a . shared_ptrweak_ptr

Múltiplos threads podem acessar simultaneamente diferentes objetos shared_ptre weak_ptrobjetos que apontam para o mesmo objeto.

O objeto referenciado deve ser protegido separadamente para garantir a segurança do thread .

shared_ptre weak_ptrsão baseados em versões usadas pelas bibliotecas Boost . O C ++ Technical Report 1 (TR1) primeiro os apresentou ao padrão, como utilitários gerais , mas o C ++ 11 adiciona mais funções, em linha com a versão Boost.

Veja também

Referências

Leitura adicional

links externos