paulo1205
(usa Ubuntu)
Enviado em 28/11/2022 - 03:17h
Sei que o tópico já está marcado como resolvido, mas eu comecei a escrever uma resposta dias atrás, que acabei não enviando antes porque me faltou tempo de terminar. Envio-a agora, na esperança de que possa ser útil.
Na medida do possível, seria bom evitar misturar uso muito frequente de objetos de tipos semelhantes, porém distintos e não-relacionados entre si, no mesmo programa. Tem certeza de que não dá para preferir um tipo, em relação ao outro? O que justificaria usar ambos ao mesmo tempo?
Solução perfeita na existe, então o que você teria de fazer dependeria de algumas perguntas, tais como:
• É realmente necessário ter cada um dos tipos?
• Para cada tipo, ele é usado integralmente e de modo significativo, ou apenas usam-se apenas alguns de seus membros espradicamente e isoladamente?
• É simples e barato converter um tipo no outro?
• Converter do
tipo1 para o
tipo2 é mais simples e barato do que converter do
tipo2 para o
tipo1 ?
• Que informação importante seria perdida com as respectivas conversões de um tipo no outro?
• Quão frequentemente teriam de ser feitas as conversões em cada sentido?
• Operações que eventualmente usassem o valor convertido poderiam usar
r-values , ou todas as conversões teria de ser explicitamente guardadas numa variável?
• Após uma conversão de tipo, eu poderia executar várias operações antes de converter de volta, ou teria de converter de volta para o tipo original após cada operação com o tipo convertido?
• Valeria a pena usar um dos tipos como classe base para um tipo derivado mais do que outro tipo (por exemplo: um deles tem mais funções virtuais do que outro, particularmente um destrutor virtual, permitindo melhor polimorfismo)?
• Eventuais coleções de objetos poderiam ser polimórficas, ou usariam objetos concretos?
Não conheço nenhuma das bibliotecas que você mencionou, mas até para implementar a sugestão dada acima pelo Leandro, acima, seria interessante responder primeiro as questões acima.
Veja alguns esqueletos de solução, que podem ser mais ou menos adequados, a depender das respostas.
// Funções conversoras de tipos entre os tipos X e Y.
X to_X(const Y &y){ return X(/* alguma computação usando membros de y */); }
Y to_Y(const X &x){ return Y(/* alguma computação usando membros de x */); }
void f(){
X x;
/* ... */
func_with_X_param(x);
func_with_Y_param_by_value(to_Y(x));
Y y=to_Y(x);
func_with_Y_pointer_param(&y);
x=to_X(y);
/* ... */
}
// X facilita o polimorfismo, com funções virtuais que eu posso estender, então
// crio uma classe derivada my_X que herda de X e contém um Y.
class my_X: public X {
private:
mutable Y y_; // declaro como mutable para poder recalcular y_
// a partir de outros membros, mesmo em objetos que
// sejam logicamente constantes.
mutable bool outdated_y;
public:
my_X(/* parâmetros típicos de X (e talvez de Y) */):
X(/* parâmetros típicos de X */),
y_(/* parâmetros típicos de Y (ou calculados pelos de X) */),
outdated_y(false)
{
/* Ajustes dos parâmetros herdados de X e do campo y_. */
}
// Força atualização do campo do tipo Y.
void update_y(){
/* Ajusta o valor de y_ de acordo com os campos herados de X. */
outdated_y=false;
}
// Getter do valor de y_.
// Vale-se do fato de y_ ser mutable para recalculá-lo, caso
// necessário, mesmo que o objeto esteja identificado como constante.
Y y() const {
if(outdated_y){
/* Ajusta o valor de y_ de acordo com os campos herados de X. */
outdated_y=false;
}
return y_;
}
/* Setter para o valor de y_. */
void y(/* Parâmetros que alteram y_ */){
y_=Y(/* alguma coisa em função dos parâmetros */);
/* Recalcula/atualiza atributos herdados de X. */
}
// Função virtual herdada de X.
void virt_func(/* argumentos */) override {
X::virt_func(/* argumentos (talvez levando y_ em consideração, se estiver atualizado). */);
outdated_y=true; // Apenas marca como desatualizado (só calcula
// novamente quando precisar; mas você pode
// optar por fazer atualizando sempre).
}
};
void func_using_Y(const Y &){ }
void func(){
std::vector<std::shared_ptr<X>> collection;
collection.emplace_back(new X);
collection.emplace_back(new my_X(/* ... */));
/* ... */
for(auto &spx: collection){ // ‘spx’ é de “Shared Pointer para X”.
spx->virt_func(/* ... */); // Chama função virtual; se o objeto for
// do tipo my_X, chama my_X::virt_func().
if(const my_X *pmx=dynamic_cast<my_X *>(spx.get()); pmx){
func_using_Y(pmx->y()); // Sendo o ponteiro mx para um objeto cons-
// tante, chama o getter, que é constante
// (mas como o campo y_ é mutable, o getter
// pode eventualmente recalcular seu valor).
}
/* ... */
spx->non_virt_func(/* ... */); // Ao chamar uma função de X que não é
// virtual, tal função não vai saber
// que alguns objetos (do tipo my_X)
// têm campos adicionais.
if(my_X *pmx=dynamic_cast<my_X *>(spx.get()); pmx){
pmx->update_y(); // Força atualização de y_, uma vez que a cha-
// mada a non_virt_func() seguramente não atua-
// lizou os campos que não fazem parte de X.
}
}
}
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)