paulo1205
(usa Ubuntu)
Enviado em 04/09/2023 - 01:06h
Por que você precisa de um
void * ?
Em C++ (e em Java, C#, D, outras tantas linguagens de OO, e até em C, se você resolver usar GObject ou outra técnica de emulação de OO), o que se costuma fazer quando você tem uma coleção de objetos que não serão necessariamente todos do mesmo tipo é usar uma classe base abstrata, que define a interface (funções-membros virtuais/métodos) comum a todos esses objetos, e derivar a partir dessa classe em todos os objetos que possam vir a fazer parte de tal coleção, que teria de ser uma coleção de ponteiros para/referências a objetos da classe base.
O exemplo clássico seria um vetor de
forma s, uma classe abstrata que vai ter várias classes concretas delas derivadas.
#include <iostream>
#include <memory>
#include <vector>
#include <cmath>
#include <cstdlib>
class forma {
private:
/* declaração dos atributos comuns a todas as formas */
public:
forma(){ /* ... */ } // um ou mais construtores dos atributos comuns
virtual ~forma(){ /* ... */ } // destrutor virtual, para que uma coleção de ponteiros/referências possa invocar o destrutor correto
// API comum a todas as classes derivadas
virtual void exibe() = 0; // função virtual pura: obrigatório que cada classe concreta forneça sua própria implementação.
virtual void desloca(double dx, double dy) = 0; // idem
virtual void redimensiona(double fator) = 0; // idem
/* etc. */
};
class quadrado: public forma {
private:
double m_x_esq, m_y_topo, m_l;
public:
quadrado(double x_esq=-.5, double y_topo=-.5, double lado=1.0): forma(), m_x_esq(x_esq), m_y_topo(y_topo), m_l(std::abs(lado)) { }
virtual ~quadrado(){ }
void exibe(){ /* desenha o quadrado */ }
void desloca(double dx, double dy){ m_x_esq+=dx; m_y_topo+=dy; }
void redimensiona(double fator){ m_l*=std::abs(fator); }
/* etc. */
};
class circulo: public forma {
private:
double m_xc, m_yc, m_r;
public:
circulo(double xc=0, double yc=0, double raio=1.0): forma(), m_xc(xc), m_yc(yc), m_r(std::abs(raio)) { }
virtual ~circulo(){ }
double raio() const { return m_r; }
void exibe(){ /* desenha o círculo */ }
void desloca(double dx, double dy){ m_xc+=dx; m_yc+=dy; }
void redimensiona(double fator){ m_r*=std::abs(fator); }
/* etc. */
};
class triangulo: public forma {
private:
/* ... */
public:
void exibe(){ /* desenha o triângulo */ }
void desloca(double, double){ /* ... */ }
void redimensiona(double){ /* ... */}
/* etc. */
};
using colecao_formas_t=std::vector<std::unique_ptr<forma>>;
/* Desenha todos os objetos de uma coleção de objetos. */
void exibe_colecao(const colecao_formas_t &col){
for(const auto &pforma: col)
pforma->exibe();
}
/*** Exemplos de varruda da coleção procurando objetos de um tipo específico, mas que necessariamente é derivado de forma. ***/
/* Conta apenas os quadrados contidos na coleção. */
size_t conta_quadrados(const colecao_formas_t &col){
size_t count=0;
for(const auto &pforma: col)
if(const quadrado *pqdr=dynamic_cast<const quadrado *>(pforma.get()); pqdr!=nullptr)
++count;
return count;
}
/* Desenha apenas os círculos contidos da coleção. */
void exibe_circulos(const colecao_formas_t &col){
for(const auto &pforma: col)
if(const circulo *pcir=dynamic_cast<const circulo *>(pforma.get()); pcir!=nullptr)
const_cast<circulo *>(pcir)->exibe(); // Como exibe() não está declarado como const, preciso de um const_cast aqui.
// XXX: O certo mesmo seria declarar exibe() como const, se isso for possível
// (e.g. “virtual void exibe() const = 0;”).
}
int main(){
colecao_formas_t colecao_formas;
for(const auto pf: std::initializer_list<forma *>{new quadrado(-M_SQRT1_2, -M_SQRT1_2, M_SQRT2), new circulo(/*...*/), new triangulo(/*...*/), /* ... */})
colecao_formas.emplace_back(pf);
std::cout << "Nº de quadrados: " << conta_quadrados(colecao_formas) << '\n';
}
O que possivelmente não é recomendável — e talvez tenha sido isso o que lhe desencorajaram de fazer — seria ter uma coleção de ponteiros para qualquer coisa (
void * ), e converter essa coisa desconhecida para alguma classe potencialmente diferente do tipo que deveria ter. O compilador não vai reclamar, mesmo com todos os níveis de diagnóstico e alertas ligados, se você fizer, por exemplo, o seguinte, embora seja claramente um código inválido.
/*** CRIANÇAS, NÃO TENTEM ISTO EM CASA!!! ***/
#include <iostream>
#include <string>
int main(){
void *ptr=new std::string("Hello, World!");
*(static_cast<std::ostream *>(ptr)) << "Again I say: hello, world.";
}
Em particular, o que você falou de converter de
void * para o tipo que você quer usando
dynamic_cast não funciona diretamente — para fazer isso, você teria primeiro de converter de
void * para um ponteiro para a classe base usando
static_cast , e depois converter desse ponteiro-base para um ponteiro da classe derivada usando
dynamic_cast , certificando-se que o resultado da conversão não é nulo (sem contar eventuais passos adicionais com
const_cast , se um ou mais dos ponteiros no meio do caminho apontarem para dados constantes). Parece-me natural que essa dupla (ou tripla, ou quádrupla) conversão poderia ser evitada se, em vez de uma coleção de dados de tipos indeterminados e disjuntos, você conseguisse modelar tal coleção para usar essa tal classe base comum, tratando os tipos derivados por meio das funções-membros virtuais sempre que possível, e com
dynamic_cast apenas quando estritamente necessário, como o exemplo das formas, acima, mostra.
Se, de todo, for impossível usar uma classe base comum, considere, antes de recorrer a
void * , a possibilidade de usar
std::variant ou
std::any como tipo dos elementos da sua coleção.
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)