«

»

dic 14

Il C++ e 5 malefici cast

Gandal ehm Dumbledore that casts a spell

Gandalfl Dumbledore that casts a spell

In C è facile spararti in un piede;
In Cpp è più difficile ma se ci riesci ti salta via l’intera gamba.
– Bjarne Stroustrup

Una cosa che dico sempre del Cpp è che è un linguaggio che va dominato e che insegnarlo a un principiante sarebbe come dare un mitragliatore al tipo che entra per la prima volta al poligono di tiro.
Ci sono un sacco di funzioni che sono pensate seguendo uno di questi due principi:

  1. La macchina può farlo && tu sei un uomo responsabile –> il compilatore te lo deve permettere.
  2. Nel caso ti trovassi impantanato con una particolare coppia di design pattern che non avresti dovuto usare insieme una certa funzionalità potrebbe essere utile && tu sei un uomo responsabile –> il compilatore te lo deve permettere.

La cosa che dico per chiarire questo concetto è di solito:
"In CPP puoi addirittura scrivere sopra le costanti!"

Oggi voglio parlare di questo mostro cercando però di far capire che se usato correttamente ha un comportamento addirittura più sicuro di quello del C.

Il C++ e i cast

In Cpp esistono ben 5 tipi di cast ordinabili in base alla loro maleficenza nel modo seguente:

dynamic_cast

E’ il tipo di cast più importante quando si lavora ad oggetti, ed è stata un ottima idea.
Permette di effettuare un cast solo se il cast è possibile e il controllo viene fatto sia dal compilatore (controllo statico) sia in fase di runtime ( controllo dinamico, da cui il nome)
E’ stato pensato per castare gli oggetti in caso di ereditarietà e polimorfismo:
Il cast è sempre possibile quando si converte una classe derivata nella sua classe madre.

class Punto2{
	public:
	short x;
	short y;

	Punto2(short x, short y){
		this->x=x;
		this->y=y;
	}

};

class Punto3 : public Punto2{
	public :
	float z;
	Punto3(float x, float y, float z): Punto2(x,y){
		this->z=z;
	}

};


int main(){
	Punto3 p3_1=Punto3(1,2,3);
	Punto2 *p2_1=dynamic_cast<Punto2*>(&p3_1);
	//Questa non funzionerà
	Punto3 *p3_2=dynamic_cast<Punto3*>(p2_1);

	getwchar();
}

Il primo cast di questo codice funziona, il secondo da errore.

Il comportamento in caso di errore di dynamic_cast , nel caso l’analisi statica del compilatore sia incapace di rilevare un errore, è abbastanza strano:

  • Se stiamo castando verso un puntatore ci darà un puntatore nullo
  • Se stiamo castando verso una reference si genererà un eccezione bad_cast

dynamic_cast non funziona nelle classi che non hanno metodi virtuali e, per corollario, non funziona sulle struct.

Quando potrebbe essere utile?

Sempre, quando si fa downcasting di oggetti polimorfici.
Bisogna però tenere a mente che è leggermente più lento di static_cast in quanto fa dei controlli a runtime.

static_cast

E’ sostanzialmente identico al C Cast, con in vantaggio di essere facilmente individuabile nel codice senza dover tirar fuori un espressione regolare ma usando un semplice "trova" ( ehh, quando ero ggiovane gli ide non erano così belli).
Permette di effettuare un cast solo se il cast è possibile e la possibilità viene vagliata solo dal compilatore. Quindi non ci sono controlli a runtime.
Permette di fare cose brutte… ma cose brutte che si possono fare anche in C.

int main(){
	Punto3 p3_1=Punto3(1,2,3);
	Punto2 *p2_1=dynamic_cast<Punto2*>(&p3_1);
	void* mistero=p2_1;
	//Punto3 è incompatibile con Punto2 ed è pure più grande. questa è una cosa pericolosa!
	Punto3 *p3_2=(Punto3*)(mistero);
	Punto3 *p3_3=static_cast<Punto3*>(mistero);
	//Dynamic cast non permette di farlo
	Punto3 *p3_4=dynamic_cast<Punto3*>(mistero);
	getwchar();
}

Quando potrebbe essere utile

Andrebbe sempre usato al posto del C Cast, ma di fatto non lo si usa quasi mai…purtroppo.

reinterpret_cast

Nell’esempio precedente abbiamo ingannato il compilatore convincendolo a fare un cast pericoloso, ma il compilatore è nostro amico, perchè ingannarlo? Cpp ci offre un modo per dirgli di fare cose cattive.

int main(){
	Punto3 p3_1=Punto3(1,2,3);
	Punto2 *p2_1=dynamic_cast<Punto2*>(&p3_1);
	void* mistero=p2_1;
	// Punto3 è incompatibile con Punto2 ed è pure più grande. questa è una cosa pericolosa!
	Punto3 *p3_2=(Punto3*)(mistero);
	Punto3 *p3_3=static_cast<Punto3*>(mistero);
	// Tu ordini, il compilatore lo fa.
	Punto3 *p3_4=reinterpret_cast<Punto3*>(p2_1);
	getwchar();
}

reinterpret_cast non fa nessun controllo sul contenuto, casta e basta.

Quando potrebbe essere utile

Quasi mai. Solo se dovete fare cose veramente strane o se siete persone cattive.
Non lamentatevi se vi siete sparati in un piede… avete premuto voi il grilletto,avete pure tolto la sicura.

const_cast

Eccolo qui il piccolo mostro… leggiamo prima la definizione del modificatore const dal K&R

The qualifier const can be applied to the declaration of any variable to specify that its value
will not be changed
. For an array, the const qualifier says that the elements will not be altered.

Ehh, ma in fondo anche le costanti stanno su locazioni della ram… sono variabili! e quindi il nostro compilatore deve permetterci di modificarle! anche in C possiamo farlo ma dobbiamo ingannare il compilatore.
Invece il compilatore Cpp non è solo nostro amico, si fida proprio di noi e non dobbiamo fargli credere di star facendo qualcosa di buono come a suo nonno compilatore C che va ogni domenica alla chiesa di K&R a venerare UnixSystemV.

			int main() {
				const int a = 0;
				int* b = (int*)&a; // C
				c = const_cast<int*>(&a); // Cpp
			}

Quando potrebbe essere utile

Quasi mai. Solo se dovete fare cose veramente strane E se siete persone cattive.
Non lamentatevi se vi siete sparati in un piede… avete premuto voi il grilletto,avete pure tolto la sicura, avete messo voi le pallottole a punta cava per farvi ancora più male.

C Cast

E’ il cast classico del C, sorpresi di vederlo qui sotto?

int main() {
	const int a = 0;
	int* b = (int*)&a; // C
	c = const_cast<int*>(&a); // Cpp
}

Quando potrebbe essere utile?

Sempre quando non si lavora con oggetti.
Quando si lavora con oggetti lo si può usare per fare tutte le stranezze che si possono fare con gli altri cast, ma in modo più oscuro.

Piccola digressione: static_cast e dynamic_cast nelle classi polimorfiche

Nella parte precedente del post mi sono concentrato fondamentalmente su quello che succede ai dati, ma gli oggetti sono fatti anche di funzioni.
Contrariamente a quanto si possa pensare, in caso di successo, static_cast e dynamic_cast hanno comportamenti assolutamente identici con le funzioni polimorfiche.

L’OOP basata sulle classi si basa su 3 concetti:

  • Incapsulamento
  • Ereditarietà
  • Polimorfismo

Cpp implementa e controlla il polimorfismo attraverso il modificatore virtual.

Un metodo polimorfico di cui si è fatto l’override copre il metodo della classe base anche in caso di cast. con tutti i tipi di cast.
Invece nel caso di metodi non virtual il cast fa in modo che vengano chiamati quelli della classe destinazione.

Per accedere a un metodo virtual della classe base bisogna fare qualcosa di sporco: oggetto.Classebase::metodo()

class Punto2{
	public:
	short x;
	short y;

	Punto2(short x, short y){
		this->x=x;
		this->y=y;
	}

	virtual void toString(){
		cout << x << "," << y << endl;
	}

	void toString2(){
		cout << x << "," << y << endl;
	}

};

class Punto3 : public Punto2{
	public :
	float z;
	Punto3(float x, float y, float z): Punto2(x,y){
		this->z=z;
	}
	virtual void toString(){
		cout << x << "," << y << "," << z << endl;
	}

	void toString2(){
		cout << x << "," << y << "," << z << endl;
	}

};

int main() {
	Punto3 p3_1=Punto3(1,2,3);
	p3_1.toString();
	p3_1.toString2();
	cout << "Dynamic Cast" << endl;
	Punto2 *p2_1=dynamic_cast<Punto2*>(&p3_1);
	p2_1->toString();
	p2_1->toString2();
	cout << "Static Cast" << endl;
	Punto2 *p2_2=static_cast<Punto2*>(&p3_1);
	p2_2->toString();
	p2_2->toString2();
	cout << "C Cast" << endl;
	Punto2 *p2_3=(Punto2*)(&p3_1);
	p2_3->toString();
	p2_3->toString2();
	cout << "Reinterpret Cast" << endl;
	Punto2 *p2_4=reinterpret_cast<Punto2*>(&p3_1);
	p2_4->toString();
	p2_4->toString2();
	cout << "Per accedere a un metodo virtuale dalla classe base bisogna fare questo" << endl;
	p3_1.Punto2::toString();

	getwchar();
}

che da come output:

	1,2,3
        1,2,3
        Dynamic Cast
        1,2,3
        1,2
        Static Cast
        1,2,3
        1,2
        C Cast
        1,2,3
        1,2
        Reinterpret Cast
        1,2,3
        1,2
        Per accedere a un metodo virtuale dalla classe base bisogna fare questo
        1,2

Note finali

Questo post è stato scritto in Markdown nei ritagli di tempo, e poi incollato su wordpress.
(quasi) tutti i codici sono stati provati in remoto con Koding. Spero non ci siano errori.

Share Button

1 comment

  1. Antonio Salsi

    Articolo fatto bene e finalmente divertente… Mi sono gatto anche 4 risate. Grandissimo

Commenti disabilitati.