Pergunta

Quais são algumas maneiras que você pode atirar no próprio pé quando se utiliza boost::shared_ptr ? Em outras palavras, o que armadilhas que eu tenho que evitar quando uso boost::shared_ptr ?

Foi útil?

Solução

referências cíclicas: a shared_ptr<> para algo que tem um shared_ptr<> ao objeto original. Você pode usar weak_ptr<> de quebrar este ciclo, é claro.


eu adicionar o seguinte como um exemplo do que estou a falar nos comentários.

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        if (parent_) parent_->frob();
    }

private :
    void do_frob();
    shared_ptr<node> parent_;
    vector< shared_ptr<node> > children_;
};

Neste exemplo, você tem uma árvore de nós, cada um dos quais contém um ponteiro para seu pai. A função de membro do FROB (), por qualquer motivo, ondulações para cima através da árvore. (Isto não é totalmente estranho; alguns frameworks GUI trabalhar desta forma).

O problema é que, se você referência perder para o nó de nível superior, em seguida, o nó superior ainda mantém fortes referências a seus filhos, e todos os seus filhos também possuem uma forte referência aos seus pais. Isto significa que há referências circulares, mantendo todas as instâncias de si mesmos limpeza, enquanto não há nenhuma maneira de realmente alcançar a árvore a partir do código, vazamentos essa memória.

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
        if (parent) parent->frob();
    }

private :
    void do_frob();
    weak_ptr<node> parent_; // Note: now a weak_ptr<>
    vector< shared_ptr<node> > children_;
};

Aqui, o nó pai foi substituído por um ponteiro fraco. Já não tem uma palavra a dizer no tempo de vida do nó ao qual se refere. Assim, se o nó superior sai do escopo, como no exemplo anterior, em seguida, ao mesmo tempo que mantém fortes referências a seus filhos, seus filhos não possuem fortes referências a seus pais. Assim, não há fortes referências ao objeto, e limpa-se para cima. Por sua vez, isso faz com que as crianças a perder a sua única referência forte, o que faz com que eles para limpar, e assim por diante. Em suma, este não vai vazamento. E, assim, substituindo estrategicamente um shared_ptr <> com um weak_ptr <>.

Nota:. O acima se aplica igualmente a std :: shared_ptr <> e std :: weak_ptr <> como faz para boost :: shared_ptr <> e boost :: weak_ptr <>

Outras dicas

Criação de múltiplos shared_ptr alheios é ao mesmo objeto:

#include <stdio.h>
#include "boost/shared_ptr.hpp"

class foo
{
public:
    foo() { printf( "foo()\n"); }

    ~foo() { printf( "~foo()\n"); }
};

typedef boost::shared_ptr<foo> pFoo_t;

void doSomething( pFoo_t p)
{
    printf( "doing something...\n");
}

void doSomethingElse( pFoo_t p)
{
    printf( "doing something else...\n");
}

int main() {
    foo* pFoo = new foo;

    doSomething( pFoo_t( pFoo));
    doSomethingElse( pFoo_t( pFoo));

    return 0;
}

Construindo um ponteiro compartilhada temporária anônima, por exemplo no interior dos argumentos para uma chamada de função:

f(shared_ptr<Foo>(new Foo()), g());

Isso é porque ele é permitido para o new Foo() a ser executado, em seguida, g() chamado, e g() para lançar uma exceção, sem a shared_ptr sempre a ser criadas, de modo que o shared_ptr não tem a chance de limpar o objeto Foo.

Seja tomada cuidado dois ponteiros para o mesmo objeto.

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer

b->doSomething(); // crashes

em vez usar este

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d = 
    boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--

b->doSomething(); // no crash

Além disso, todas as classes segurando shared_ptrs devem definir construtores de cópia e operadores de atribuição.

Não tente usar shared_from_this () no construtor - não vai funcionar. Em vez disso criar um método estático para criar a classe e tê-lo retornar um shared_ptr.

Eu já passou referências a shared_ptrs sem problemas. Apenas certifique-se que é copiado antes de ser salvo (ou seja, sem referências como membros da classe).

Aqui estão duas coisas a evitar:

  • Chamar a função get() para obter o ponteiro bruto e usá-lo depois que o apontado para objeto sai do escopo.

  • Passando uma referência ou um ponteiro bruto para um shared_ptr deve ser perigoso também, uma vez que não irá incrementar a contagem interna que ajuda a manter o objeto vivo.

Nós depurar várias semanas comportamento estranho.

O motivo foi:
passamos 'isto' para alguns trabalhadores de rosca em vez de 'shared_from_this'.

Não exatamente um footgun, mas certamente uma fonte de frustração até que você quebrar a cabeça em torno de como fazê-lo da maneira C ++ 0x: a maioria dos predicados que você conhece e amor de <functional> não jogar bem com shared_ptr. Felizmente, std::tr1::mem_fn trabalha com objetos, ponteiros e shared_ptrs, substituindo std::mem_fun, mas se você quiser usar std::negate, std::not1, std::plus ou qualquer um desses velhos amigos com shared_ptr, estar preparado para obter acolhedor com std::tr1::bind e provavelmente espaços reservados argumento bem. Na prática, isso é realmente muito mais genérica, já que agora você basicamente acabam usando bind para cada adaptador de função de objeto, mas isso levará algum tempo para se acostumar, se você já está familiarizado com as funções de conveniência do STL.

Este artigo DDJ toques sobre o assunto, com lotes de exemplo de código. Eu também Blogged sobre isso há alguns anos atrás quando eu tinha que descobrir como fazê-lo .

Usando shared_ptr para realmente pequenos objetos (como char short) poderia ser uma sobrecarga se você tem um monte de pequenos objetos no montão, mas eles não são realmente "compartilhada". aloca boost::shared_ptr 16 bytes para cada novo referência contar que cria no g ++ 4.4.3 e VS2008 com impulso 1,42. std::tr1::shared_ptr aloca 20 bytes. Agora, se você tem um milhão de shared_ptr<char> distinta Isso significa que 20 milhões de bytes de memória são ido em manter apenas count = 1. para não mencionar os custos indireç~ao e fragmentação de memória. Tente com o seguinte em sua plataforma favorita.

void * operator new (size_t size) {
  std::cout << "size = " << size << std::endl;
  void *ptr = malloc(size);
  if(!ptr) throw std::bad_alloc();
  return ptr;
}
void operator delete (void *p) {
  free(p);
}

Dando um shared_ptr para isso dentro de uma definição de classe também é perigoso. Use enabled_shared_from_this vez.

Veja o post seguinte aqui

Você precisa ter cuidado quando você usa shared_ptr em código multithread. É então relativamente fácil tornar-se em um caso quando par de shared_ptrs, apontando para a mesma memória, é usado por diferentes threads.

O uso generalizado popular de shared_ptr quase inevitavelmente causa ocupação de memória indesejada e invisível.

referências cíclicas são uma causa conhecida e alguns deles podem ser indireta e difícil de detectar, especialmente em código complexo que é trabalhado por mais de um programador; um programador pode decidir de um objeto precisa de uma referência para outro como uma solução rápida e não tem tempo para examinar todo o código para ver se ele está fechando um ciclo. Este perigo é extremamente subestimada.

Menos bem entendido é o problema de referências inéditas. Se um objeto é compartilhado com muitos shared_ptrs então ele não será destruído até que cada um deles é zerado ou sai do escopo. É muito fácil ignorar uma dessas referências e acabar com objetos ocultos invisível na memória que você pensou que tinha acabado com.

Embora estritamente falando estes não são vazamentos de memória (tudo vai ser lançado antes de sair do programa) que são tão prejudiciais e mais difícil de detectar.

Estes problemas são as consequências de falsas declarações expediente: 1. Declarar o que você realmente quer ser propriedade única como shared_ptr. scoped_ptr seria correcto mas, em seguida, qualquer outra referência a esse objecto terá que ser um ponteiro em bruto, que pode ser deixado pendurado. 2. Declarar o que você realmente quer ser uma referência passiva observando como shared_ptr. weak_ptr seria correto, mas então você tem o incômodo de convertê-lo para share_ptr cada vez que você quiser usá-lo.

Eu suspeito que o seu projecto é um bom exemplo do tipo de problema que esta prática você pode obter em.

Se você tem uma aplicação intensiva de memória você realmente precisa de propriedade única para que seu projeto pode controlar explicitamente vida dos objetos.

Com única propriedade opObject = NULL; vai certamente excluir o objeto e ele vai fazê-lo agora.

Com propriedade compartilhada spObject = NULL; ........ quem sabe? ......

Se você tem um registro dos objetos compartilhados (uma lista de todas as instâncias ativas, por exemplo), os objetos nunca será libertado. Solução:. Como no caso das estruturas de dependência circular (ver resposta de Kaz Dragão), o uso weak_ptr conforme apropriado

Ponteiros inteligentes não são para tudo, e ponteiros crus não pode ser eliminado

Provavelmente o pior perigo é que, desde shared_ptr é uma ferramenta útil, as pessoas vão começar a colocá-lo em todos os lugares. Desde ponteiros simples pode ser mal utilizado, as mesmas pessoas vão caçar ponteiros crus e tentar substituí-los com cordas, recipientes ou ponteiros inteligentes, mesmo quando não faz sentido. utilizações legítimas ponteiros crus vai se tornar suspeito. Haverá um policial ponteiro.

Este não é apenas provavelmente o pior perigo, pode ser o perigo apenas grave. Todos os piores abusos de shared_ptr será a consequência direta da ideia de que ponteiros inteligentes são superiores a ponteiro bruto (seja lá o que isso signifique), e que colocando ponteiros inteligentes em todos os lugares farão C ++ programação "mais seguro".

É claro que o simples facto de um smart necessidades ponteiro para ser convertido para um ponteiro bruto para ser refuta usou essa alegação do culto ponteiro inteligente, mas o fato de que o acesso ponteiro bruto é "implícito" em operator*, operator-> (ou explícita em get()), mas não implícita em uma conversão implícita, é suficiente para dar a impressão de que isso não é realmente uma conversão, e que o ponteiro bruto produzido por este não conversão é um inofensivo temporário.

C ++ não pode ser feita uma "linguagem seguro", e nenhum subconjunto útil de C ++ é "seguro"

É claro que a busca de um subconjunto de segurança ( "seguro" no sentido estrito de "seguro de memória", como LISP, Haskell, Java ...) do C ++ está condenado a ser interminável e insatisfatória, como o subconjunto segura de C ++ é pequena e quase inútil, como primitivos inseguros são a regra e não a excepção. segurança memória rigorosa em C ++ significaria sem ponteiros e apenas referências com classe de armazenamento automático . Mas, em uma linguagem que o programador tem a confiança de definição , algumas pessoas vão insistir em usar alguns (em princípio) à prova de idiota "ponteiro inteligente", mesmo quando não há outra vantagem sobre ponteiros crus que < em> uma maneira específica para aparafusar o estado do programa é evitado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top