paulo1205
(usa Ubuntu)
Enviado em 18/10/2018 - 10:21h
Nesse caso você terá de fazer adaptações.
Se
Entity será uma classe base, o primeiro passo é definir bem a interface que será utilizada por todos os seus descendentes, por meio de funções-membros virtuais. Em particular, o destrutor
tem de ser virtual.
Um tipo de função-membro que pode ser útil, dependendo de como você modelar suas classes, é o que se conhece como
construtor virtual, que é uma função virtual sobreposta em cada classe descendente que constrói um objeto novo do seu próprio tipo e retorna um ponteiro para o objeto recém-construído. Como os ponteiros para objetos da classe derivada podem ser atribuídos a ponteiros para a classe base, esses ponteiros podem ser armazenados no
container, e o emprego de funções virtuais garante que cada objeto vai executar as funções corretas da sua própria classe.
Há dois tipos principais de construtores virtuais, os de duplicação de objetos já existentes, que invocam construtores de cópia para copiar a si próprios, e os de geração de semelhantes, que invocam um construtor de objetos de seu próprio tipo, mas com outros argumentos.
A classe
Entity poderia então ser alterada para se parecer com o seguinte.
class Entity {
protected:
/* Membros de dados e funções que sejam comuns a TODAS as classes derivadas. */
public:
/* Não haverá construtores públicos, pois a classe é abstrata, e não permite criar objetos diretamente. */
// O destrutor tem de ser virtual.
virtual ~Entity(){ }
/* Construtores virtuais. */
virtual Entity *clone() const = 0; // Pode ser const, pois não altera o objeto usado para chamá-lo.
virtual Entity *move() = 0; // Usa um construtor de movimentação para mover este objeto para o novo,
// colocando em seu lugar conteúdo neutro para ser destruído.
virtual Entity *make_new(void *arg) const = 0; // Cada classe derivada interpreta arg a seu modo.
// etc.
/* Outras funções da interface pública (exemplos). */
void print() const = 0;
void save_to_disk() const = 0;
// etc.
};
Na classe
Elements, as versões da função-membro
add() que derreferenciavam os ponteiros para invocar construtores de cópia agora terão de invocar os construtores virtuais.
class Elements {
/* ... */
public:
/* ... */
void add(const Entity *p){ entities.emplace_back(p->clone()); }
void add(Entity *p){ add(const_cast<const Entity *>(p)); }
void add(const pEntity &e){ entities.emplace_back(e->clone()); }
void add(pEntity &&e){ entities.emplace_back(std::move(e)); }
void add(pEntity &e){ entities,emplace_back(e); }
/* ... */
};
A primeira versão de
add() da postagem anterior, que encaminhava argumentos diretamente a um construtor de
Entity, não mais seria válida, até porque objetos da classe abstrata
Entity não poderiam mais ser construídos diretamente. No entanto, pode-se criar novas versões, tanto baseadas em
templates quanto que usem construtores virtuais.
class Elements {
/* ... */
public:
/* ... */
// A função abaixo tem de ser invocada na forma
// “add<ClasseDerivada>(lista_de_argumentos_do_construtor_da_ClasseDerivada)”.
template <class DerivEntity, class... ArgTypes> void add(ArgTypes&&... args){
entities.emplace_back(new DerivEntity(std::forward<ArgTypes>(args)...));
}
// Para objetos preexistentes que não sejam passados como ponteiros, podemos
// usar passagem por referência de Entity, sem necessidade de templates.
void add(const Entity &e){ entities.emplace_back(e.clone()); }
void add(Entity &&e){ entities.emplace_back(e.move()); }
void add(const Entity &e, void *arg){ entities.emplace_back(e.make_new(arg)); }
/* ... */
};
Pelo bloco acima, sendo
elems um objeto da classe
Elements, “
elems.add<DerivEntity>()” produziria o mesmo resultado que “
elems.add(DerivEntity())”. Contudo a primeira versão tenderá a ser mais eficiente, pois chama diretamente o construtor da classe derivada e atribui o novo objeto no
container, ao passo que a segunda opção cria um objeto como
rvalue e depois invoca um dos construtores virtuais desse objeto temporário para mover seus dados para outro objeto, e ainda chama um destrutor sobre o objeto temporário inicial.
De modo semelhante, “
elems.add(SomeEntity(), &std::make_tuple(lista, de, argumentos))” poderia ser usado como alternativa a “
elems.add<SomeEntity>(lista, de, argumentos)”, só que eu acho que raramente se vai preferir aquele a este.
NOTA: Eu falei à beça, mas não testei nada do que vai acima. Pode ser que haja erros. Use com moderação.