Pergunta

Eu estou trabalhando em um aplicativo C ++ que tem internamente alguns objetos de controlador que são criados e destruídos regularmente (usando novo). É necessário que esses controladores registar-se com um outro objeto (vamos chamá-lo controllerSupervisor), e cancelar o registro quando eles próprios são destruídos.

O problema que estou enfrentando agora está acontecendo quando eu sair da aplicação: como ordem de destruição não é determinista, acontece que a instância controllerSupervisor único é destruído antes de (alguns) dos controladores de si mesmos, e quando eles chamam o unregister método em seu destruidor, eles fazê-lo mediante um objeto já destruído.

A única idéia que eu vim com até agora (tendo um grande frio, por isso pode não significar muito) não está tendo o controllerSupervisor como uma variável global na pilha, mas sim na pilha (ou seja, usando novo). No entanto, nesse caso, eu não tenho um lugar para excluí-lo (isto é tudo em um terceiro tipo do partido da biblioteca).

Qualquer dicas / sugestões sobre o que opções possíveis são seria apreciada.

Foi útil?

Solução

Você pode usar o padrão Observer. Um controlador comunica a ele do supervisor que está sendo destruído. E da Autoridade comunica o mesmo para a sua criança sobre destruição.

Dê uma olhada http://en.wikipedia.org/wiki/Observer_pattern

Outras dicas

A ordem de destruição das variáveis ??automáticas (que incluem variáveis ??locais "normais" que você usa em funções) está na ordem inversa da sua criação. Então coloque o controllerSupervisor no topo.

Ordem de destruição de globals também está na inversa da sua criação, que por sua vez depende da ordem em que eles são definidos: objetos posteriormente definidos são criados mais tarde. Mas cuidado:. Objetos definidos em diferentes arquivos .cpp (unidades de tradução) não são garantidos para criado em qualquer ordem definida

Eu acho que você deve considerar a usá-lo como Mike recomendado:

  1. Criação é feito usando o padrão Singleton (uma vez que a ordem de inicialização de objectos em diferentes unidades de tradução não são definidos) na primeira utilização, por devolver um apontador para um objecto supervisor de função-estático.
  2. O supervisor é normalmente destruído (usando as regras sobre a destruição da estática em funções). controladores de cancelar o registro usando uma função estática do supervisor. Que um verifica se o supervisor já é destruído (verificação de um ponteiro para != 0). Se for, então nada é feito. Caso contrário, o supervisor é notificado.

Uma vez que imagino que poderia ser um supervisor, sem um controlador de estar ligado (e se apenas temporária), um ponteiro inteligente não poderia ser usado para destruir o supervisor automaticamente.

Não é, basicamente, um capítulo inteiro sobre este tema em Design ++ Modern C do Alexandrescu (Chaper 6, Singletons). Ele define uma única classe que pode gerenciar dependências, mesmo entre os próprios singletons.

Todo o livro é altamente recomendado também BTW.

Um par de sugestões:

  • fazer a controllerSupervisor um singleton (ou envolvê-la em um objeto singleton você cria para o efeito), que é acessado através de um método estático que retorna um ponteiro, em seguida, os dtors dos objetos registrados podem chamar o acessor estática (as quais no caso do desligamento do aplicativo eo controllerSupervisor foi destruído retornará NULL) e esses objetos podem evitar chamar o método de-registro nesse caso.

  • criar o controllerSupervisor na pilha usando novas e usar algo como boost::shared_ptr<> para gerenciar sua vida útil. Distribua o shared_ptr<> no método de acesso estática do singleton.

GNU gcc / g ++ fornece atributos não portáteis para tipos que são muito úteis. Um desses atributos é init_priority que define a ordem em que os objetos globais são construídas e, como consequência, a ordem inversa em que eles são destruídos. Do homem:

init_priority (prioridade)

 In Standard C++, objects defined at namespace scope are guaranteed
 to be initialized in an order in strict accordance with that of
 their definitions _in a given translation unit_.  No guarantee is
 made for initializations across translation units.  However, GNU
 C++ allows users to control the order of initialization of objects
 defined at namespace scope with the init_priority attribute by
 specifying a relative PRIORITY, a constant integral expression
 currently bounded between 101 and 65535 inclusive.  Lower numbers
 indicate a higher priority.

 In the following example, `A' would normally be created before
 `B', but the `init_priority' attribute has reversed that order:

      Some_Class  A  __attribute__ ((init_priority (2000)));
      Some_Class  B  __attribute__ ((init_priority (543)));

 Note that the particular values of PRIORITY do not matter; only
 their relative ordering.

Você poderia fazer qualquer um dos seguintes de acordo com as circunstâncias.

  1. Use o padrão Observer como sugerido por Gurin. Basicamente as informa o supervisor controladores que ele está indo para baixo ...
  2. Tenha o supervisor "próprios" os controladores e ser responsável pela sua destruição quando ele vai para baixo.
  3. Mantenha os controladores em shared_pointers assim quem desce último fará a destruição real.
  4. Gerir ambos os (ponteiros inteligentes para os controladores e) o supervisor na pilha que permitirá que você para determinar a ordem de destruição
  5. outros ...

Você poderia olhar usando o número de controladores registrados como sentinela para exclusão real.

A chamada de exclusão é, então, apenas um pedido e você tem que esperar até que os controladores de cancelar o registro.

Como mencionado este é um uso do padrão de observador.

class Supervisor {
public:
    Supervisor() : inDeleteMode_(false) {}

    void deleteWhenDone() {
        inDeleteMode_ = true;
        if( controllers_.empty()){
            delete this;
        }
    }

    void deregister(Controller* controller) {
        controllers_.erase(
            remove(controllers_.begin(), 
                        controllers_.end(), 
                        controller));
        if( inDeleteMode_ && controllers_.empty()){
            delete this;
        }
    }


private:

    ~Supervisor() {}
    bool inDeleteMode_;
    vector<Controllers*> controllers_;
};

Supervisor* supervisor = Supervisor();
...
supervisor->deleteWhenDone();

Não é exatamente elegante, mas você poderia fazer algo como isto:

struct ControllerCoordinator {
    Supervisor supervisor;
    set<Controller *> controllers;

    ~ControllerDeallocator() {
        set<Controller *>::iterator i;
        for (i = controllers.begin(); i != controllers.end(); ++i) {
            delete *i;
        }
    }
}

New mundial:

ControllerCoordinator control;

Onde quer que você construir um controlador, adicione control.supervisor.insert(controller). Onde quer que você destruir um, adicione control.erase(controller). Você pode ser capaz de evitar o prefixo control. adicionando uma referência global para control.supervisor.

O membro supervisor do coordenador não será destruído até após o destruidor é executado, então você tem a garantia de que o supervisor vai sobreviver os controladores.

Que tal ter o supervisor cuidar de destruindo os controladores?

Ok, como sugerido na literatura fazer o supervisor de um Singleton (ou de modo semelhante o objecto controlado, isto é, com escopo para uma sessão).

Use os guardas apropriados (theading, etc) em torno de Singleton, se necessário.

// -- client code --
class ControllerClient {
public:
    ControllerClient() : 
        controller_(NULL)
        {
            controller_ = Controller::create();
        }

    ~ControllerClient() {
        delete controller_;
    }
    Controller* controller_;
};

// -- library code --
class Supervisor {
public: 
    static Supervisor& getIt() {        
        if (!theSupervisor ) {
            theSupervisor = Supervisor();
        }
        return *theSupervisor;
    } 

    void deregister(Controller& controller) {
        remove( controller );
        if( controllers_.empty() ) {
            theSupervisor = NULL;
            delete this;
        }       
    }

private:    
    Supervisor() {} 

    vector<Controller*> controllers_;

    static Supervisor* theSupervisor;
};

class Controller {
public: 
    static Controller* create() {
        return new Controller(Supervisor::getIt()); 
    } 

    ~Controller() {
        supervisor_->deregister(*this);
        supervisor_ = NULL;
    }
private:    
    Controller(Supervisor& supervisor) : 
        supervisor_(&supervisor)
        {}
}

Enquanto feio este pode ser o método mais simples:

basta colocar um try catch em torno da chamada unregister. Você não tem que mudar um monte de código e desde que o aplicativo já está sendo desligado, não é um grande negócio. (Ou existem outras ratificações para a ordem de desligamento?)

Outros têm apontado melhores projetos, mas este é simples. (E feio)

Eu prefiro o observador padrão bem neste caso.

Você pode usar eventos para sinalizar destruição de Controladores

Adicionar WaitForMultipleObjects no destruidor de Supervisor que irá esperar até que todos os controladores são destruídos.

Em destruidor de controladores pode levantada evento controlador Sair.

Você precisa manter a matriz global de alças evento de saída para cada controlador.

Faça o supervisor cotrol um singelton. Certifique-se de que um construtor de controle recebe o supervisor durante a construção (não afterwords). Isso garante que o supervisor de controle é totalmente construído antes do controle. Thuse o destruidor será chamado antes do destructor supervisor controle.

class CS
{
    public:
        static CS& getInstance()
        {
            static CS  instance;
            return instance;
        }
        void doregister(C const&);
        void unregister(C const&);
    private:
        CS()
        {  // initialised
        }
        CS(CS const&);              // DO NOT IMPLEMENT
        void operator=(CS const&);  // DO NOT IMPLEMENT
 };

 class C
 {
      public:
          C()
          {
              CS::getInstance().doregister(*this);
          }
          ~C()
          {
              CS::getInstance().unregister(*this);
          }
 };

C ++ especifica padrão da ordem de inicialização / destruição, quando as variáveis ??em causa todos ajuste em um arquivo ( "unidade de tradução"). Qualquer coisa que se estende por mais de um arquivo torna-se não-portáteis.

Eu segundo as sugestões para que o Supervisor destruir cada controlador. Este é o segmento de seguros no sentido de que apenas o Supervisor diz alguém para destruir a si mesmos (ninguém-se destrói por conta própria), então não há nenhuma condição de corrida. Você também teria que evitar quaisquer possibilidades de impasse. (Dica: certifique-se os controladores podem destruir-se uma vez que já foi dito para sem a necessidade qualquer else do Supervisor)


É possível fazer este segmento seguro, mesmo que as necessidades de controlador para ser destruído antes do final do programa (isto é, se os controladores podem ser de curta duração), então eles (ou outra pessoa).

Primeiro, ele não pode ser uma condição de corrida que se preocupar se eu decidir me destruir e microssegundos depois, o Supervisor decide me destruir e me diz isso.

Em segundo lugar, se você está preocupado com essa condição de corrida você pode corrigi-lo por, digamos, exigindo que todos os pedidos de destruição passar por Supervisor. Eu quero destruir a mim mesmo que quer dizer a Autoridade para me dizer ou me registrar que a intenção com o Supervisor. Se alguém - incluindo o Supervisor - quer me destruído, eles fazem isso através do Supervisor

.

quando li o título a esta pergunta eu imediatamente me perguntei "Se houvesse uma maneira que qualquer objeto poderia garantir que ele foi destruído (destruidos?) Passado, então o que aconteceria se dois objetos adotou esse método?"

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