Pergunta para o Paulo: por que não se deve usar dynamic_cast<const_cast<Typo>>(ptr))?

1. Pergunta para o Paulo: por que não se deve usar dynamic_cast<const_cast<Typo>>(ptr))?

Samuel Leonardo
SamL

(usa XUbuntu)

Enviado em 31/08/2023 - 09:09h

De repente me veio a dúvida porque vi algumas pessoas fazendo isso:
dynamic_cast<const_cast<Type *>>(ptr))
Procurei procurei mas nada achei a respeito dos motivos reais do porque não se usar tal código.

Tem papagaio gaiato que fica repetindo que não se deve usar, mas se tu pergunta o porquê o sujeito foge do assunto e não retorna mais (como vi no stackoverflow).

E ai vim perguntar ao Paulo se ele tem alguma explicação do porquê não se deve usar tal código e o que fazer quando se precisa usar?

Alguma ideia?



  


2. Re: Pergunta para o Paulo: por que não se deve usar dynamic_cast<const_cast<Typo>>(ptr))?

Paulo
paulo1205

(usa Ubuntu)

Enviado em 03/09/2023 - 04:59h

SamL escreveu:

De repente me veio a dúvida porque vi algumas pessoas fazendo isso:
dynamic_cast<const_cast<Type *>>(ptr))
Procurei procurei mas nada achei a respeito dos motivos reais do porque não se usar tal código.

Tem papagaio gaiato que fica repetindo que não se deve usar, mas se tu pergunta o porquê o sujeito foge do assunto e não retorna mais (como vi no stackoverflow).

E ai vim perguntar ao Paulo se ele tem alguma explicação do porquê não se deve usar tal código e o que fazer quando se precisa usar?

Alguma ideia?


Além do fato de que seria um erro de sintaxe?

Suponho que a construção a que você se refere seria algo como “dynamic_cast<classe_derivada *>(const_cast<classe_base *>(ptr))”, sendo ptr um ponteiro para objeto constante do tipo classe_base (i.e. declarado com “const classe_base *ptr;”).

Nunca vi essa recomendação contrária a que você se referiu, mas também acho que nunca precisei de uma construção parecida, na qual precisasse tanto de remover o qualificador const do objeto apontado quanto testar se o ponteiro apontava para um objeto real de classe derivada, de modo que nunca fui forçado a ler material específico a esse respeito.

Não sou language lawyer, mas em princípio, com a sintaxe correta, não vejo por que tal construção seria um problema. O seguinte programa compilou normalmente com as opções -std=c++17 -Wall -Wextra -Werror -pedantic-errors -O2 e rodou sem problemas.
#include <iostream>


class base {
public:
virtual const base *f() const { return this; }
};

class deriv: public base {
public:
const deriv *f() const { return this; }
};


void f(const base *cp){
auto ptr=dynamic_cast<deriv *>(const_cast<base *>(cp));
std::cout << ptr << '\n';
}

void g(const base *cp){
auto ptr=const_cast<deriv *>(dynamic_cast<const deriv *>(cp));
std::cout << ptr << '\n';
}

void h(const base *cp){
auto ptr=dynamic_cast<const deriv *>(cp);
std::cout << ptr << '\n';
}


int main(){
const base b;
const deriv d;
f(&b); f(b.f());
f(&d); f(d.f());
g(&b); g(b.f());
g(&d); g(d.f());
h(&b); h(b.f());
h(&d); h(d.f());
}


O caso que você tem em mente difere muito do que está ali?


... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)


3. Re: Pergunta para o Paulo: por que não se deve usar dynamic_cast<const_cast<Typo>>(ptr))?

Samuel Leonardo
SamL

(usa XUbuntu)

Enviado em 03/09/2023 - 19:34h


paulo1205 escreveu:
...
O caso que você tem em mente difere muito do que está ali?

Não difere nada do que eu pensei, Paulo.

Acontece que fiquei em dúvida de continuar usando porque tive uns problemas com "ponteiros selvagens".
Tipo, detectei num código meu que eu passava um ponteiro com tipo não constante (convertido normalmente com dynamic_cast deum void *) e ai do nada (depois de passar numa função) o ponteiro derreferenciava e ai a função retornava um valor diferente do que eu tinha passado.
Minhas suspeitas cairam em cima de um código que tava fazendo examente uma conversão com const_cast, mas como eu fiz outra forma de dirblar esse problema, acabei deixando a solução ideal de lado.

E procurando no stackoverflow, vi muitos comentários dizendo pra não fazer esse tipo de código mas ningu-ém em especial explicava os motivos reais.
E o único problema que tive com conversão de const_cast foi só nesse caso ai que citei, mas também convenhamos, eu to usando um ponteiro void * e ai pode ser que o ponteiro selgaem venha dai.

E aproveitando pra te perguntar: como eu devo trocar esse void * de forma que eu tenha um "ponteiro universal"?
Acho muito feio usar void * mas como não conheço outra alternativa, fiquei com essa no momento, apesar de ter causado problema uma vez, ainda tá no código.

Obrigado ai pela ideia.

https://nerdki.blogspot.com/ acessa ai, é grátis
Não gostou? O ícone da casinha é serventia do site!


4. Re: Pergunta para o Paulo: por que não se deve usar dynamic_cast<const_cast<Typo>>(ptr))?

Paulo
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 formas, 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)


5. Re: Pergunta para o Paulo: por que não se deve usar dynamic_cast<const_cast<Typo>>(ptr))?

Samuel Leonardo
SamL

(usa XUbuntu)

Enviado em 04/09/2023 - 19:28h


paulo1205 escreveu:

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 formas, 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)

O ruim é que não tem como ter uma classe base, por conta que é uma lib externa que criei no projeto e tem de ser o mais genérico possível, visto que não é possível ter um tipo específico para ser a base, por nesse caso se trata apenas de "dados cru".
Só precisa disso pra acessar dados via callback nessa lib e ai acho que o std::any é a melhor opção e nem lembrei de considerar como uma antes hahaha

Vou testar aqui com std::any e ver se fica mais "elegante" o código.

Obrigado, Paulo!


https://nerdki.blogspot.com/ acessa ai, é grátis
Não gostou? O ícone da casinha é serventia do site!






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts