Qual seria a maneira mais segura de armazenar objetos de classes derivadas de uma interface comum em um contêiner comum?

StackOverflow https://stackoverflow.com/questions/2489533

Pergunta

Eu gostaria de gerenciar vários objetos de classes derivadas de uma classe de interface compartilhada em um contêiner comum.

Para ilustrar o problema, digamos que estou construindo um jogo que contenha diferentes atores. Vamos chamar a interface IActor e derivar Enemy e Civilian a partir dele.

Agora, a idéia é fazer com que meu loop principal de jogo seja capaz de fazer isso:

// somewhere during init
std::vector<IActor> ActorList;
Enemy EvilGuy; 
Civilian CoolGuy;
ActorList.push_back(EvilGuy);
ActorList.push_back(CoolGuy);

e

// main loop
while(!done) {
    BOOST_FOREACH(IActor CurrentActor, ActorList) {
        CurrentActor.Update();
        CurrentActor.Draw();
    }
}

... Ou algo nesse sentido. Este exemplo obviamente não funcionará, mas essa é a razão pela qual estou perguntando aqui.

Eu gostaria de saber: qual seria a melhor, mais segura e de nível mais alto de gerenciar esses objetos em um recipiente heterogêneo comum? Conheço uma variedade de abordagens (Boost :: qualquer, void*, classe Handler com Boost :: shared_ptr, boost.pointer contêiner, dinâmico_cast), mas não consigo decidir qual seria o caminho a seguir aqui.

Além disso, gostaria de enfatizar que quero ficar longe o mais longe possível do gerenciamento manual de memória ou de indicadores aninhados.

Ajude muito apreciado :).

Foi útil?

Solução

Como você imaginou, precisa armazenar os objetos como indicadores.
Eu prefiro usar os contêineres do ponteiro de impulso (em vez de um recipiente normal de indicadores inteligentes).

A razão para isso é que o contêiner PTR Boost acesse os objetos como se fossem objetos (referências devolvendo) em vez de ponteiros. Isso facilita o uso de funções e algoritmos padrão nos contêineres.

A desvantagem dos indicadores inteligentes é que você está compartilhando a propriedade.
Não é isso que você realmente quer. Você deseja que a propriedade esteja em um único local (neste caso, o contêiner).

boost::ptr_vector<IActor> ActorList; 
ActorList.push_back(new Enemy()); 
ActorList.push_back(new Civilian());

e

std::for_each(ActorList.begin(), 
              ActorList.end(),
              std::mem_fun_ref(&IActor::updateDraw));

Outras dicas

Para resolver o problema que você mencionou, embora esteja indo na direção certa, mas está fazendo isso da maneira errada. Isso é o que você precisaria fazer

  • Defina uma classe base (que você já está fazendo) com funções virtuais que seriam substituídas por classes derivadas Enemy e Civilian no seu caso.
  • Você precisa escolher um contêiner adequado com o seu objeto. Você tomou um std::vector<IActor> o que não é uma boa escolha porque
    • Em primeiro lugar, quando você está adicionando objetos ao vetor, ele está levando ao fatiamento de objetos. Isso significa que apenas o IActor parte de Enemy ou Civilian está sendo armazenado em vez de todo o objeto.
    • Em segundo lugar, você precisa chamar funções, dependendo do tipo de objeto (virtual functions), o que só pode acontecer se você usar ponteiros.

Ambas as razões acima apontam para o fato de que você precisa usar um recipiente que possa conter ponteiros, algo como std::vector<IActor*> . Mas uma escolha melhor seria usar container of smart pointers que o salvará das dores de cabeça do gerenciamento de memória. Você pode usar qualquer um dos ponteiros inteligentes, dependendo da sua necessidade (mas não auto_ptr)

É assim que seu código seria

// somewhere during init
std::vector<some_smart_ptr<IActor> > ActorList;
ActorList.push_back(some_smart_ptr(new Enemy()));
ActorList.push_back(some_smart_ptr(new Civilian()));

e

// main loop
while(!done) 
{
    BOOST_FOREACH(some_smart_ptr<IActor> CurrentActor, ActorList) 
    {
        CurrentActor->Update();
        CurrentActor->Draw();
     }
}

O que é praticamente semelhante ao seu código original, exceto para a parte de ponteiros inteligentes

Minha reação instantânea é que você deve armazenar ponteiros inteligentes no contêiner e verifique se a classe base define métodos virtuais suficientes (puros) que você nunca precisa dynamic_cast de volta à classe derivada.

Se você deseja que o contêiner possua exclusivamente os elementos nele, use um contêiner de ponteiro de impulso: eles são projetados para esse trabalho. Caso contrário, use um recipiente de shared_ptr<IActor> (e, claro, use -os corretamente, o que significa que todos que precisam compartilhar a propriedade usam shared_ptr).

Nos dois casos, verifique se o destruidor de IActor é virtual.

void* Exige que você faça gerenciamento manual de memória, então isso está fora. Boost. qualquer coisa é exagerada quando os tipos são relacionados pela herança - o polimorfismo padrão faz o trabalho.

Se você precisa dynamic_cast ou não é um problema ortogonal - se os usuários do contêiner precisarem apenas da interface Iactor, e você (a) faz todas as funções da interface virtual, ou então (b) use o idioma da interface não virtual, então você não Precisa de dinâmico_cast. Se os usuários do contêiner souberem que alguns dos objetos do iactor são "realmente" civis e desejam fazer uso de coisas que estão na interface civil, mas não no Iactor, você precisará de elenco (ou um redesenho).

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