Pergunta

Eu tenho sido extensivamente usando ponteiros inteligentes (boost :: shared_ptr para ser exato) em meus projetos nos últimos dois anos. I compreender e apreciar os seus benefícios e eu geralmente gosto muito deles. Mas quanto mais eu usá-los, mais eu perca o comportamento determinista do C ++ com relação ao gerenciamento de memória e RAII que parece que eu gosto em uma linguagem de programação. ponteiros inteligentes simplificar o processo de gerenciamento de memória e fornecer coleta de lixo automática entre outras coisas, mas o problema é que o uso de coleta de lixo automática no ponteiro geral e inteligente introduz especificamente algum grau de indeterminisim na ordem de (de) inicializações. Este indeterminismo toma o controle longe dos programadores e, como eu vim a perceber ultimamente, faz o trabalho de concepção e APIs em desenvolvimento, o uso de que não é completamente conhecido antecipadamente no momento do desenvolvimento, irritantemente morosa todos os padrões de uso e casos de canto deve ser bem pensado.

Para elaborar mais, estou actualmente a desenvolver uma API. Partes deste API requer certos objetos a serem inicializados antes ou destruídas após outros objetos. Dito de outra forma, a ordem de inicialização (de) é importante, às vezes. Para lhe dar um exemplo simples, digamos que temos uma classe chamada do sistema. Um sistema fornece algumas funcionalidades básicas (logging no nosso exemplo) e possui uma série de subsistemas via ponteiros inteligentes.

class System {
public:
    boost::shared_ptr< Subsystem > GetSubsystem( unsigned int index ) {
        assert( index < mSubsystems.size() );
        return mSubsystems[ index ];
    }

    void LogMessage( const std::string& message ) {
        std::cout << message << std::endl;
    }

private:
    typedef std::vector< boost::shared_ptr< Subsystem > > SubsystemList;
    SubsystemList mSubsystems;    
};

class Subsystem {
public:
    Subsystem( System* pParentSystem )
         : mpParentSystem( pParentSystem ) {
    }

    ~Subsystem() {
         pParentSubsystem->LogMessage( "Destroying..." );
         // Destroy this subsystem: deallocate memory, release resource, etc.             
    }

    /*
     Other stuff here
    */

private:
    System * pParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
};

Como você já pode dizer, um subsistema só faz sentido no contexto de um sistema. Mas um subsistema de tal projeto pode facilmente sobreviver a sua controladora do sistema.

int main() {
    {
        boost::shared_ptr< Subsystem > pSomeSubsystem;
        {
            boost::shared_ptr< System > pSystem( new System );
            pSomeSubsystem = pSystem->GetSubsystem( /* some index */ );

        } // Our System would go out of scope and be destroyed here, but the Subsystem that pSomeSubsystem points to will not be destroyed.

     } // pSomeSubsystem would go out of scope here but wait a second, how are we going to log messages in Subsystem's destructor?! Its parent System is destroyed after all. BOOM!

    return 0;
}

Se tivéssemos usado ponteiros crus para subsistemas de espera, que teria destruído subsistemas quando o nosso sistema tinha ido para baixo, é claro, em seguida, pSomeSubsystem seria um apontador pendente.

Embora, não é o trabalho de um designer de API para proteger os programadores cliente de si mesmos, é uma boa idéia para fazer a API fácil de usar corretamente e difícil de usar incorretamente. Então eu estou pedindo a vocês. O que você acha? Como devo aliviar este problema? Como você projetar tal sistema?

Agradecemos antecipadamente, Josh

Foi útil?

Solução

Resumo do problema

Existem duas preocupações concorrentes nesta questão.

  1. gestão do ciclo de vida de Subsystems, permitindo sua remoção no momento certo.
  2. Os clientes de Subsystems precisa saber que o Subsystem eles estão usando é válido.

Manipulação # 1

System detém os Subsystems e deve gerir o seu ciclo de vida com seu próprio escopo. Usando shared_ptrs para isso é particularmente útil uma vez que simplifica destruição, mas você não deve ser entregá-los, porque então você solta o determinismo que você está procurando com relação à sua desalocação.

Manipulação nº 2

Esta é a preocupação mais interessante para endereço. Descrevendo o problema com mais detalhes, você precisa de clientes para receber um objeto que se comporta como um Subsystem enquanto que Subsystem (e é System pai) existe, mas se comporta de forma adequada após uma Subsystem é destruído.

Este é facilmente resolvido por uma combinação da Proxy Padrão , o href="http://en.wikipedia.org/wiki/State_pattern" rel="nofollow noreferrer" title="Wikipedia: Estado padrão"> Estado Padrão e Null Objecto Padrão . Embora isso possa parecer ser um complexo de uma solução, pouco ' Há uma simplicidade única a ser tido no outro lado da complexidade .' Como desenvolvedores Biblioteca / API, é preciso ir a milha extra para tornar os nossos sistemas robustos. Além disso, queremos que os nossos sistemas a se comportar de forma intuitiva como um usuário espera, e à decadência graciosamente quando tentam abusar deles. Existem muitas soluções para este problema, no entanto, isso deve-se levá-lo a que todo ponto importante onde, como você e Scott Meyers dizer, é " fácil de usar corretamente e difícil de usar incorretamente '

Agora, estou assumindo que, na realidade, trata System em alguma classe base do Subsystems, a partir do qual derivam vários Subsystems diferentes. Eu introduzi-lo abaixo como SubsystemBase. Você precisa introduzir um Proxy objeto, SubsystemProxy abaixo, que implementa a interface de SubsystemBase encaminhando solicitações para o objeto que é proxy. (Neste sentido, é muito parecido com uma aplicação de propósito específico do Decorator Padrão). Cada Subsystem cria um desses objetos, que detém através de um shared_ptr, e retorna quando solicitado via GetProxy(), que é chamado pelo objeto System pai quando GetSubsystem() é chamado.

Quando um System sai do escopo, cada um de seus objetos Subsystem fica destruído. Em seu destructor, que eles chamam mProxy->Nullify(), o que provoca a sua Proxy objetos para mudar sua Estado . Eles fazem isso alterando a apontar para um Null objeto , que implementa a interface SubsystemBase, mas fá-lo por não fazer nada.

Usando o Pattern Estado aqui tem permitido a aplicação do cliente para ser completamente alheio à existência ou não de um Subsystem particular. Além disso, ele não precisa verificar ponteiros ou manter em torno casos que deveriam ter sido destruídas.

O Padrão Proxy permite que o cliente para ser dependente de um objeto leve quecompletamente embrulha-se os detalhes do funcionamento interno da API, e mantém uma constante, interface uniforme.

O Null Objecto Padrão permite que o Proxy para a função após a Subsystem original tenha sido removida.

Exemplo de código

Eu tinha colocado uma pseudo-código de exemplo de qualidade áspera aqui, mas eu não estava satisfeito com ele. Eu reescrito-lo para ser um preciso, a compilação (eu usei g ++) exemplo do que eu descrevi acima. Para obtê-lo para o trabalho, eu tive que introduzir algumas outras classes, mas seus usos devem ser claras de seus nomes. I empregou o Singleton Pattern para a classe NullSubsystem, como faz sentido que você não precisa mais de um. ProxyableSubsystemBase abstrai completamente o comportamento Proxying longe do Subsystem, permitindo que ele seja ignorante deste comportamento. Aqui está a UML Diagrama das classes:

UML Diagrama de Subsistema e Hierarquia do sistema

Exemplo de código:

#include <iostream>
#include <string>
#include <vector>

#include <boost/shared_ptr.hpp>


// Forward Declarations to allow friending
class System;
class ProxyableSubsystemBase;

// Base defining the interface for Subsystems
class SubsystemBase
{
  public:
    // pure virtual functions
    virtual void DoSomething(void) = 0;
    virtual int GetSize(void) = 0;

    virtual ~SubsystemBase() {} // virtual destructor for base class
};


// Null Object Pattern: an object which implements the interface to do nothing.
class NullSubsystem : public SubsystemBase
{
  public:
    // implements pure virtual functions from SubsystemBase to do nothing.
    void DoSomething(void) { }
    int GetSize(void) { return -1; }

    // Singleton Pattern: We only ever need one NullSubsystem, so we'll enforce that
    static NullSubsystem *instance()
    {
      static NullSubsystem singletonInstance;
      return &singletonInstance;
    }

  private:
    NullSubsystem() {}  // private constructor to inforce Singleton Pattern
};


// Proxy Pattern: An object that takes the place of another to provide better
//   control over the uses of that object
class SubsystemProxy : public SubsystemBase
{
  friend class ProxyableSubsystemBase;

  public:
    SubsystemProxy(SubsystemBase *ProxiedSubsystem)
      : mProxied(ProxiedSubsystem)
      {
      }

    // implements pure virtual functions from SubsystemBase to forward to mProxied
    void DoSomething(void) { mProxied->DoSomething(); }
    int  GetSize(void) { return mProxied->GetSize(); }

  protected:
    // State Pattern: the initial state of the SubsystemProxy is to point to a
    //  valid SubsytemBase, which is passed into the constructor.  Calling Nullify()
    //  causes a change in the internal state to point to a NullSubsystem, which allows
    //  the proxy to still perform correctly, despite the Subsystem going out of scope.
    void Nullify()
    {
        mProxied=NullSubsystem::instance();
    }

  private:
      SubsystemBase *mProxied;
};


// A Base for real Subsystems to add the Proxying behavior
class ProxyableSubsystemBase : public SubsystemBase
{
  friend class System;  // Allow system to call our GetProxy() method.

  public:
    ProxyableSubsystemBase()
      : mProxy(new SubsystemProxy(this)) // create our proxy object
    {
    }
    ~ProxyableSubsystemBase()
    {
      mProxy->Nullify(); // inform our proxy object we are going away
    }

  protected:
    boost::shared_ptr<SubsystemProxy> GetProxy() { return mProxy; }

  private:
    boost::shared_ptr<SubsystemProxy> mProxy;
};


// the managing system
class System
{
  public:
    typedef boost::shared_ptr< SubsystemProxy > SubsystemHandle;
    typedef boost::shared_ptr< ProxyableSubsystemBase > SubsystemPtr;

    SubsystemHandle GetSubsystem( unsigned int index )
    {
        assert( index < mSubsystems.size() );
        return mSubsystems[ index ]->GetProxy();
    }

    void LogMessage( const std::string& message )
    {
        std::cout << "  <System>: " << message << std::endl;
    }

    int AddSubsystem( ProxyableSubsystemBase *pSubsystem )
    {
      LogMessage("Adding Subsystem:");
      mSubsystems.push_back(SubsystemPtr(pSubsystem));
      return mSubsystems.size()-1;
    }

    System()
    {
      LogMessage("System is constructing.");
    }

    ~System()
    {
      LogMessage("System is going out of scope.");
    }

  private:
    // have to hold base pointers
    typedef std::vector< boost::shared_ptr<ProxyableSubsystemBase> > SubsystemList;
    SubsystemList mSubsystems;
};

// the actual Subsystem
class Subsystem : public ProxyableSubsystemBase
{
  public:
    Subsystem( System* pParentSystem, const std::string ID )
      : mParentSystem( pParentSystem )
      , mID(ID)
    {
         mParentSystem->LogMessage( "Creating... "+mID );
    }

    ~Subsystem()
    {
         mParentSystem->LogMessage( "Destroying... "+mID );
    }

    // implements pure virtual functions from SubsystemBase
    void DoSomething(void) { mParentSystem->LogMessage( mID + " is DoingSomething (tm)."); }
    int GetSize(void) { return sizeof(Subsystem); }

  private:
    System * mParentSystem; // raw pointer to avoid cycles - can also use weak_ptrs
    std::string mID;
};



//////////////////////////////////////////////////////////////////
// Actual Use Example
int main(int argc, char* argv[])
{

  std::cout << "main(): Creating Handles H1 and H2 for Subsystems. " << std::endl;
  System::SubsystemHandle H1;
  System::SubsystemHandle H2;

  std::cout << "-------------------------------------------" << std::endl;
  {
    std::cout << "  main(): Begin scope for System." << std::endl;
    System mySystem;
    int FrankIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Frank"));
    int ErnestIndex = mySystem.AddSubsystem(new Subsystem(&mySystem, "Ernest"));

    std::cout << "  main(): Assigning Subsystems to H1 and H2." << std::endl;
    H1=mySystem.GetSubsystem(FrankIndex);
    H2=mySystem.GetSubsystem(ErnestIndex);


    std::cout << "  main(): Doing something on H1 and H2." << std::endl;
    H1->DoSomething();
    H2->DoSomething();
    std::cout << "  main(): Leaving scope for System." << std::endl;
  }
  std::cout << "-------------------------------------------" << std::endl;
  std::cout << "main(): Doing something on H1 and H2. (outside System Scope.) " << std::endl;
  H1->DoSomething();
  H2->DoSomething();
  std::cout << "main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object." << std::endl;

  return 0;
}

A saída do código:

main(): Creating Handles H1 and H2 for Subsystems.
-------------------------------------------
  main(): Begin scope for System.
  <System>: System is constructing.
  <System>: Creating... Frank
  <System>: Adding Subsystem:
  <System>: Creating... Ernest
  <System>: Adding Subsystem:
  main(): Assigning Subsystems to H1 and H2.
  main(): Doing something on H1 and H2.
  <System>: Frank is DoingSomething (tm).
  <System>: Ernest is DoingSomething (tm).
  main(): Leaving scope for System.
  <System>: System is going out of scope.
  <System>: Destroying... Frank
  <System>: Destroying... Ernest
-------------------------------------------
main(): Doing something on H1 and H2. (outside System Scope.)
main(): No errors from using handles to out of scope Subsystems because of Proxy to Null Object.

Other Thoughts:

  • Um artigo interessante que li em um dos Gems Jogo de programação livros fala sobre usando Null objetos para depuração e desenvolvimento. Eles estavam falando especificamente sobre o uso de modelos e texturas Null gráficos, como uma textura xadrez para fazer modelos faltando realmente se destaca. O mesmo poderia ser aplicado aqui, alterando o NullSubsystem para uma ReportingSubsystem que registrar a chamada e, possivelmente, a pilha de chamadas sempre que for acessado. Isso permitiria que você ou clientes da sua biblioteca para rastrear onde eles estão dependendo de algo que tem ido para fora do escopo, mas sem a necessidade de causar um acidente.

  • I mencionado em um comentário @Arkadiy que a dependência circular fez subir entre System e Subsystem é um pouco desagradável. Ele pode ser facilmente sanado por ter System derivar de uma interface em que Subsystem depende, uma aplicação de Robert C Martin inversão de dependência Princípio . Melhor ainda seria isolar a funcionalidade que Subsystems precisa de seu pai, escrever uma interface para que, em seguida, agarrar um implementador de que a interface em System e passá-lo para os Subsystems, que iria segurá-la através de um shared_ptr. Por exemplo, você pode ter LoggerInterface, que seus usos Subsystem para escrever para o log, então você poderia derivar CoutLogger ou FileLogger a partir dele, e manter uma instância de tal no System.
    Eliminando a Circular Dependência

Outras dicas

Este é capaz de fazer com o uso adequado da classe weak_ptr. Na verdade, você já são bastante perto de ter uma boa solução. Está certo que você não pode esperar que "out-pensar" seus programadores do cliente, nem que você deve esperar que eles vão sempre seguir as "regras" da sua API (como eu tenho certeza que você já está ciente). Então, o melhor que você pode realmente fazer é controlar os danos.

Eu recomendo ter sua chamada para GetSubsystem retornar um weak_ptr em vez de um shared_ptr simplesmente para que o desenvolvedor cliente pode testar a validade do ponteiro sem sempre reivindicando uma referência a ele.

Da mesma forma, tem pParentSystem ser um boost::weak_ptr<System> para que ele possa detectar internamente se o seu System pai ainda existe através de uma chamada para lock em pParentSystem juntamente com um cheque de NULL (a ponteiro bruto não irá dizer-lhe isso).

Assumindo que você mudar sua classe Subsystem sempre verificar se há ou não a sua correspondente objeto System existe, você pode garantir que, se as tentativas programador cliente para usar o exterior Subsystem objeto do escopo planejado que ocorrerá um erro (que você controla) , ao invés de uma exceção inexplicável (que você deve confiar o programador cliente para catch / tratar adequadamente).

Assim, no seu exemplo com main(), as coisas não vão BOOM! A maneira mais graciosa para lidar com isso em dtor do Subsystem seria tê-lo parecido com este:

class Subsystem
{
...
  ~Subsystem() {
       boost::shared_ptr<System> my_system(pParentSystem.lock());

       if (NULL != my_system.get()) {  // only works if pParentSystem refers to a valid System object
         // now you are guaranteed this will work, since a reference is held to the System object
         my_system->LogMessage( "Destroying..." );
       }
       // Destroy this subsystem: deallocate memory, release resource, etc.             

       // when my_system goes out of scope, this may cause the associated System object to be destroyed as well (if it holds the last reference)
  }
...
};

Espero que isso ajude!

Aqui Sistema possui claramente os subsistemas e não vejo nenhum ponto em ter propriedade compartilhada. Eu simplesmente retornar um ponteiro bruto. Se um subsistema sobrevive seu Sistema, isso é um erro por conta própria.

Você estava certo no início, no seu primeiro parágrafo. Seus projetos com base em RAII (como o meu e mais bem escrito código C ++) exigem que os objetos são mantidos por ponteiros propriedade exclusiva. Em impulso que seria scoped_ptr.

Então, por que não usar scoped_ptr. Será, certamente, porque você queria os benefícios de weak_ptr para proteger contra referências pendurados, mas você só pode apontar um weak_ptr em um shared_ptr. Então você adotaram a prática comum de expediente declarando shared_ptr quando o que você realmente queria era única propriedade. Esta é uma falsa declaração e como você diz, que compromete destruidores sendo chamados na seqüência correta. Claro, se você nunca compartilhar a propriedade que você vai fugir com ele -. Mas você terá que verificar constantemente todo o seu código para se certificar de que nunca foi compartilhada

Para piorar o boost :: weak_ptr é inconveniente para uso (não tem operador ->) para que os programadores evitar este inconveniente por falsamente declarando passivo observando referências como shared_ptr. Este de ações curso propriedade e se você esquecer de nulo que shared_ptr em seguida, o objeto não são destruídas ou seu destruidor chamado quando você pretende que ele.

Em suma, você tem sido shafted pela biblioteca de impulso - ele não consegue abraçar bom C ++ programação práticas e programadores forças para fazer declarações falsas, a fim de tentar obter algum benefício a partir dele. Só é útil para o código cola scripting que realmente quer propriedade compartilhada e não está interessado em um controlo apertado sobre memória ou destruidores sendo chamado na seqüência correta.

Eu tenho sido o mesmo caminho que você. Proteção contra ponteiros pendurados é extremamente necessária em C ++, mas a biblioteca de impulso não fornece uma solução aceitável. Eu tinha que resolver este problema - o meu departamento software queria garantias de que C ++ pode ser feita segura. Então eu rolou meu próprio - foi bastante um monte de trabalho e pode ser encontrada em:

http://www.codeproject.com/KB/cpp/XONOR.aspx

É totalmente adequado para o trabalho de rosca única e estou prestes a atualizá-lo para abraçar ponteiros sendo compartilhados entre threads. Sua principal característica é que ele suporta inteligentes (self-zeroing) observadores passivos de objetos detidas.

Infelizmente programadores tornaram-se seduzido pela coleta de lixo e 'tamanho único' soluções de ponteiro inteligente e em grande parte não são sequer pensar sobre propriedade e observadores passivos - como resultado, eles nem sequer sabem que o que eles estão fazendo é errado e não se queixam. Heresia contra Boost é quase inédito!

As soluções que foram sugeridas para você são absurdamente complicada e de nenhuma ajuda em tudo. Eles são exemplos do absurdo que os resultados de uma relutância cultural a reconhecer que ponteiros de objeto têm papéis distintos que devem ser corretamente declarados e uma fé cega que o impulso deve ser a solução.

Eu não vejo um problema com ter System :: GetSubsystem retornar um ponteiro bruto (em vez de um ponteiro inteligente) para um subsistema. Desde que o cliente não é responsável pela construção dos objetos, então não há nenhum contrato implícito para o cliente para ser responsável pela limpeza. E já que é uma referência interna, que deve ser razoável supor que a vida útil do objeto Subsystem é dependente do tempo de vida do objeto do sistema. Você deve, então, reforçar este contrato implícito com a documentação afirmando tanto.

O ponto é, que você não está a reatribuição ou compartilhar propriedade -? Então, por que usar um ponteiro inteligente

O verdadeiro problema aqui é o seu design. Não há nenhuma boa solução, porque o modelo não reflete bons princípios de design. Aqui está uma regra útil de uso polegar I:

  • Se um objeto contém uma coleção de outros objetos, e pode retornar qualquer objeto arbitrário dessa coleção, então remover o objeto de seu projeto .

Eu percebo que o seu exemplo é planejado, mas é uma anti-padrão que eu vejo um monte de trabalho. Pergunte a si mesmo, o valor é System acrescentando que does not std::vector< shared_ptr<SubSystem> >? Os usuários de sua necessidade API para conhecer a interface de SubSystem (desde que você devolvê-los), assim que escrever um suporte para eles é apenas aumentar a complexidade. Pelo menos as pessoas sabem a interface para std::vector, forçando-os a lembrar GetSubsystem() acima at() ou operator[] é apenas média .

A sua pergunta é sobre o gerenciamento de vida dos objetos, mas uma vez que você começar a distribuir objetos, controle que você quer perder da vida, permitindo que outros para mantê-los vivos (shared_ptr) ou o risco de falha se eles são usados ??depois que eles foram embora (raw ponteiros). Em multi-threaded aplicações sua pior ainda - que bloqueia os objetos que estão distribuindo a diferentes tópicos? Aumenta compartilhados e ponteiros fracos são uma complexidade induzir armadilha quando usado desta forma, especialmente porque eles estão apenas enfiar o suficiente seguro para viagem até desenvolvedores inexperientes.

Se você estiver indo para criar um suporte, ele precisa esconder a complexidade de seus usuários e isentá-las dos encargos você pode gerenciar a si mesmo. Como um exemplo, uma interface que consiste em a) comando de envio para o subsistema (por exemplo, um URI -? / Sistema / subsistema / comando param = valor) e b) subsistemas ITERATE e comandos de subsistema (por meio de um stl-like iteração) e, eventualmente, c) registar subsistema lhe permitiria esconder quase todos os detalhes da sua implementação a partir de seus usuários, e fazer cumprir o tempo de vida / ordenação / requisitos de bloqueio internamente.

Uma API iteratable / enumeráveis ??é vastamente preferível expor objetos em qualquer caso - os comandos / inscrições podem ser facilmente serializados para a geração de casos de teste ou arquivos de configuração, e eles poderiam ser exibidos de forma interativa (digamos, em um controle de árvore, com diálogos composta por examinando as ações disponíveis / parâmetros). Você também seria proteger seus usuários de API de mudanças internas que você pode precisar fazer às classes subsistema.

Eu adverti-lo contra a seguir os conselhos em resposta Aarons. Projetando uma solução para um problema tão simples que requer 5 padrões de design diferentes para implementar só pode significar que o problema errado está sendo resolvido. Eu também estou cansado de quem cita o Sr. Myers em relação ao design, uma vez por sua própria admissão:

"Eu não tenho escrito software de produção em mais de 20 anos, e eu nunca escrevi produção de software em C ++. Não, nem nunca. Além disso, eu nunca sequer tentou software de produção escrita em C ++, para que não só estou não um real C desenvolvedor ++, eu não sou mesmo um aspirante. Contrabalançando este é um pouco o fato de que eu fiz software de pesquisa de escrita em C ++ durante meus anos de escola de pós-graduação (1985-1993), mas mesmo isso foi pequena (alguns milhares de linhas) -desenvolvedor único para-ser-jogado-away-rapidamente coisas. E desde riscando como consultor sobre uma dúzia de anos atrás, a programação meu C ++ tem sido limitada a toy “vamos ver como isso funciona” (ou, às vezes, “vamos ver quantas compiladores isso quebra”) programas, tipicamente programas que se encaixam em um único arquivo".

Não quer dizer que seus livros não valem leitura, mas ele não tem autoridade para falar no design ou complexidade.

No seu exemplo, seria melhor se o sistema realizou uma vector<Subsystem> em vez de um vector<shared_ptr<Subsystem> >. Seus tanto mais simples e elimina a preocupação que você tem. GetSubsystem iria retornar uma referência em seu lugar.

objetos pilha será lançado na ordem oposta à qual onde instanciado, a menos que o desenvolvedor usando o API está a tentar gerir o ponteiro inteligente, que normalmente não vai ser um problema. Há apenas algumas coisas que você não vão ser capazes de prevenir, o melhor que você pode fazer é fornecer avisos em tempo de execução, de preferência depurar somente.

O seu exemplo parece muito COM gosta de mim, você tem contagem de referência sobre os subsistemas que estão sendo devolvidos usando shared_ptr, mas você está faltando-lo no próprio objeto do sistema.

Se cada um dos objetos subsistema fez um AddRef sobre o objeto do sistema na criação, e uma liberação de destruição você poderia pelo menos apresentar uma exceção se a contagem de referência foi incorreto quando o objeto do sistema é destruído cedo.

O uso de weak_ptr também permitirá que você forneça uma mensagem em vez / aswell como explodir quando as coisas são liberados na ordem errada.

A essência do problema é uma referência circular: Sistema refere-se a Subsystem, e Subsystem, por sua vez, refere-se ao sistema. Este tipo de estrutura de dados não podem ser facilmente manipulados por contagem de referência - que exige a coleta de lixo adequada. Você está tentando quebrar o loop usando um ponteiro bruto para uma das bordas -. Isso só vai produzir mais complicações

Pelo menos duas boas soluções foram sugeridas, por isso não vou tentar superar os cartazes anteriores. Eu só posso notar que, em @ solução de Aaron você pode ter um proxy para o sistema e não para Subsistemas -. Dependingo n o que é mais complexo e que faz sentido

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