2016. október 3., hétfő

Típus átalakítás

      Az adat típusának átalakítását egy másik típusra type-cast-nak nevezik. Két típusú átalakítás lehetséges: az implicit és explicit átalakítás. Az implicit átalakításhoz nincs szükség cast-re, mert az átalakítás automatikusan megtörténik a másolásnál ha a cél memóriahelye kompatibilis adattípust tárol.

short a=2000;
int b;
b=a;

Ebben a példában a short típusú változó int típussú változóvá alakult. A hasonló átalakítások néha veszítenek az adatok pontosságából, de ezt a fordító jelzi. Nem csak az alapvető adattípusokat lehet átalakítani, hanem az osztály típusú adatokat is.

class A {};
class B { public: B (A a) {} };
A a;
B b=a;

A B osztály konstruktora egyetlen A típusú objektumot használ paraméternek, így B objektuma hasonló lesz A objektumával és az átalakítás lehetséges.

Az eltérő értelmezést igénylő átalakítások explicit átalakítások kell legyenek és ezek kétféleképp is meghatározhatóak:

short a=2000;
int b;
b = (int) a;
b = int (a);

Ezt e két operátor bármilyen átalakításnál használható, ide értve az osztály-osztály és mutató-osztály átalakítást. Habár egy ilyen átalakítás szintaktikailag helyes, mégis hibát vagy redundáns kimenetet eredményezhet.

#include<iostream>
using namespace std;

class A {
     float i,j;
};

class B {
     int x,y;
     public:
           B (int a, int b) { x=a; y=b; }
           int result() { return x+y;}
};

int main () {
     A d;
     B * padd;               // padd egy B típusú mutató
     padd = (B*) &d;         // padd az A típusú objektumra (d) mutat
     cout << padd->result(); // az A osztálynak nincs is result() függvénye
                             // mégsem jelez hibát a fordító
     return 0;
}

A fenti programban A és B osztály nem kompatibilisek egymással. Ennek ellenére az A objektumát (d) át lehet konvertálni, hogy a B mutatója (padd) rá mutasson. A padd használhatatlanná válik, és a fordító ezt nincs honnan lássa.

A static_cast operátor

      A standard C++ alapvetően négy cast operátort tartalmaz: static_cast, const_cast, reinterpret_cast és dynamic_cast. Az újabbak kevésbé használatosak, már csak azért is hogy nincsenek ennyire általánosítva. Az implicit átalakításhoz hasonlóan itt sem lehet szintaxis hibát észlelni egy hibás átalakításnál, viszont a fordítás során minden adattípus ellenőrzésre kerül. A static_cast operátor a standard adattípusok közti átalakításra jó, mint például void* -> char* vagy int -> double és ezek fordítottjai.

#include <iostream>
using std::cout;
using std::endl;

class BaseClass
{
     public:
           void f() const { cout << "Ősosztály\n"; }
};

class DerivedClass : public BaseClass
{
     public:
           void f() const { cout << "Derivált osztály\n"; }
};

void test(BaseClass*);

int main()
{
     double d = 8.22;
     int x = static_cast<int>(d); // d átalakítása int-re

     cout << "d = " << d << "\nx = " << x << endl;
    
     BaseClass *basePtr = new DerivedClass; //memóriafoglalás
     test(basePtr); //egy globális függvény
     delete basePtr; //memória felszabadítás
    
     return 0;
}

void test(BaseClass *basePtr)
{
     DerivedClass *derivedPtr; //derivalt osztály típusó mutató
     //az ősosztály típusú mutatót átalakítjuk derivált típusú mutatóvá
     derivedPtr = static_cast<DerivedClass*>(basePtr);
     derivedPtr->f(); //az f a derivált osztályból fog lefutni
}

A kimenet:
d = 8.22
x = 8
Derivált osztály

A BaseClass *basePtr = new DerivedClass utasítás létrehoz egy BaseClass típusú mutatót és rögtön rá is irányítja egy DerivedClass típusú objektumra. Ezután a mutató mint paraméter jut el a test függvénybe, ahol értékét leadja egy DerivedClass típusú mutatónak. Ehhez a static_cast segítségével ő maga is át kell alakuljon DerivedClass típussá. Ezt a műveletet downcast-nak nevezik, mert felső osztálytípusból alsó osztálytípusba történt az átalakítást. Ez általában veszélyes művelet, mert a programozónak előbb biztosra kell tudnia, hogy az objektumok kompatibilisek egymással, ebben az esetben például mindkét osztálynak rendelkeznie kell az f() függvénnyel.

virtuális függvényeket meghívó virtualViaPointer és virtualViaReference függvények esetén a fordító nem tudja meghatározni, hogy milyen típusú lesz a baseClassPtr objektum, ami akár problémák a forrása is lehet.

A const_cast operátor

      A const és volatile típusu adatok átalakítására jó. A következő programban a const_cast egy osztály tagváltozóját alakítja át, egy const tagfüggvény révén, amely normális esetben nem változtathatná meg az objektum felépítését.

#include<iostream>
using std::cout;

class ConstCastTest
{
     public:
           void setNumber(int);
           int getNumber() const;
           void printNumber() const;
     private:
           int number;
};

void ConstCastTest::setNumber(int num) { number = num; }

int ConstCastTest::getNumber() const { return number; }

void ConstCastTest::printNumber() const
{
     cout << "\nprintNumber() = ";
     //a number módosítás hibát adna, mert a függvény const
     const_cast<ConstCastTest*>(this)->number--;
     cout << number;
}

int main()
{
     ConstCastTest x;
     x.setNumber(8);
     cout << "getNumber() = " << x.getNumber();
     x.printNumber();
    
     return 0;
}

A kimenet:
getNumber() = 8
printNumber() = 7

A ConstCastTest osztály három publikus tagfüggvényt és egy privát tagváltozót tartalmaz. Két tagfüggvény konstans, ami azt jelenti, hogy nem változtathat semmit a tagváltozón. A printNumber függvényben a const_cast<ConstCastTest*>(this)->number-- utasítás ezt mégis lehetővé teszi. A this egy ConstCastTest típusú konstans mutató, mely konstans mivoltát a const_cast ideiglenesen feloldja és ezért módosítani lehet a number értékét. A fordító nem jelez hibát. A const_cast operátort nem lehet közvetlenül konstans változón használni, hanem konstans mutatókkal vagy konstans referenciákkal működik, például:

int i = 8;
const int& ref = i;
const_cast<int&>(ref)=7;
cout << "\ni = " << i;

A const_cast operátor konstans paraméter átadására is használható nem-konstans paramétert váró függvénynek.

#include <iostream>
using namespace std;

void print (char * str){ cout << str << endl;}

int main ()
{
     const char * c = "Konstans sor";
     print ( const_cast<char *> (c) );
     return 0;
}

A reinterpret_cast operátor

      A nem standard átalakítások a reinterpret_cast operátorral történnek. Inkább különböző típusú mutatók között végez átalakítást, a standard típusokkal nem működik, mint a double -> int.

#include <iostream>
using std::cout;
#include <queue>

int main()
{
     int x = 160;
     int *ptr = &x;
    
     cout << *reinterpret_cast<char*>(ptr);
    
     return 0;
}

A kimenet:
á

A fenti programban a ptr mutató az x címével van inicializálva. Mindkettő int típus. A reinterpret_cast a mutatót char típussá alakítja. Ettől az x értéke char-ként lesz értelmezve és az eredmény az x értékének ASCII kódja, ami éppen az á betű. Mivel a reinterpret_cast platform-függő, előfordulhat hogy egy-egy platformon másképp viselkedik.

Run-Time Type Information (RTTI)

      Az RTTI lehetővé teszi egy objektum típusának meghatározását a program futása alatt. A következő példák a typeid és a dynamic_cast operátorokat mutatják be:

#include <iostream>
using std::cout;
using std::endl;
#include <typeinfo>

template <class T>
T maximum(T value1, T value2, T value3)
{
     T max = value1;
     if(value2 > value1) max = value2;
     if(value3 > max) max = value3;
     const char* dataType = typeid(T).name();
     cout << "A(z) " << dataType << " típusú adatok közül a legnagyobb: ";
     return max;
}

int main()
{
     int a = 8, b = 88, c = 22;
     double d = 95.96, e = 78.59, f = 83.99;
    
     cout << maximum(a, b, c) << endl;
     cout << maximum(d, e, f) << endl;
    
     return 0;
}

A kimenet:
A(z) i típusú adatok közül a legnagyobb: 88
A(z) d típusú adatok közül a legnagyobb: 95.96

A typeid használatához szükséged alkalmazni a <typeinfo> headert. A const char* dataType = typeid(T).name(); utasítás az, amelyik visszatéríti a T aktuális típusának nevét. A typeid egy operátor, amely egy referenciát térít vissza egy type_info objektumról, amely egy adattípust képvisel. A typeid-t nem ajánlott switch-ben használni. Helyette inkább használjunk virtuális függvényeket.

A dynamic_cast operátor olyan típus átalakításokra jó, amely a program során történik és a fordító nem észlelheti. Általában downcasting-re használják a programozók, azaz amikor az alaposztály objektumát lekonvertálják egy derivált osztály objektumává. Ezt csakis mutatókkal vagy referenciákkal lehet használni. Az RTTI olyan öröklési hierarchiákban játszik szerepet, amely rendelkezik a polimorfizmussal.

#include <iostream>
using std::cout;
using std::endl;
const double PI = 3.14159;

class Shape
{
     public:
           virtual double area() const { return 0.0; }
};

class Circle : public Shape
{
     public:
           Circle(int r = 1) { radius = r; }
           virtual double area() const { return PI * radius * radius; }
     protected:
           int radius;
};

class Cylinder : public Circle
{
     public:
           Cylinder(int h = 1) { height = h; }
           virtual double area() const { return 2 * PI * radius * height + 2 * Circle::area(); }
     private:
           int height;
};

void outputShapeArea(const Shape*);

int main()
{
     Circle circle;
     Cylinder cylinder;
     Shape *ptr = 0;
     outputShapeArea(&circle);       //a kör területe
     outputShapeArea(&cylinder);     //a henger területe
     outputShapeArea(ptr);           //a test területe
     return 0;
}

void outputShapeArea(const Shape* shapePtr)
{
     const Circle *circlePtr;
     const Cylinder *cylinderPtr;
    
     cylinderPtr = dynamic_cast<const Cylinder*>(shapePtr); //Shape->Cylinder
    
     if(cylinderPtr != 0) //ha át lehetett alakítani Cylinderbe
           cout << "A henger területe: " << shapePtr->area();
     else
     {
           circlePtr = dynamic_cast<const Circle*>(shapePtr); //Shape->Circle
           if(circlePtr != 0) //ha át lehetett alakítani Circlebe
                cout << "A kör területe: " << circlePtr->area();
           else
                cout << "Nem kör és nem henger.";
     }
     cout << endl;
}

A kimenet:
A kör területe: 3.14159
A henger területe: 12.5664
Nem kör és nem henger.

A program a Shape ősosztályt használja, mely rendelkezik egy virtuális függvénnyel, egy Circle derivált osztállyal. A Circle-nek is van egy derivált osztálya, a Cylinder. Mindkét derivált osztály felülírja az area virtuális függvényt. A main függvényben deklarálva van egy-egy objektum a derivált osztályokból és egy mutató az ősosztályból, amely nullával van inicializálva. Az outputShapeArea függvény paramétere is egy ősosztály mutató, de ennek ellenére a main-ben derivált mutatókkal is meg van hívva. Ezt a fordító nem veszi észre, ő csak azt látja, hogy a típus rendben van. Ahhoz, hogy a program működjön is, a shapePtr mutatót downcasting-olni kell a derivált mutatókra a dynamic_cast segítségével. Ennek eredményeképp a cylinderPtr megkapja a shapePtr címét ha az const Cylinder* típus, különben zéró értéket vesz fel. Ha a kapott érték nem zéró, akkor használható az area() függvény. Ugyanez az eljárás a másik típuskonverzió esetében is.

Explicit konstruktorok

      Az Overloading című bejegyzésben bebizonyosodott, hogy bármilyen konstruktor, melynek van egy paramétere, használható implicit átalakító operátorként. Például egy char-t átalakíthat osztály típussá. Az átalakítás automatikusan történik, nem kell cast-okat használni. Bizonyos esetekben azonban az ilyen művelet hibákat eredményezhet. Az alábbi programban az Array osztály konstruktora olyan objektumot alkot, mely egy egydimenziós tömböt állít elő a paraméternek megadott elemszám alapján.

array.h
#ifndef ARRAY_H
#define ARRAY_H
#include <iostream>
using std::ostream;

class Array
{
     friend ostream &operator<<(ostream&, const Array&);
     public:
           Array(int = 10);
           ~Array();
     private:
           int size;
           int *ptr;
};
#endif

array.cpp
#include <iostream>
using std::cout;
using std::ostream;
#include <cassert>
#include "array.h"

Array::Array(int arraySize)
{
     size = (arraySize > 0 ? arraySize : 0);
     cout << "Konstruktor: "<< size;
     ptr = new int[size];
     assert(ptr != 0);
     for(int i = 0; i < size; i++)
           ptr[i] = 0;
}

Array::~Array() { delete [] ptr; }

ostream &operator<<(ostream &output, const Array&a)
{
     int i;
     for(i = 0; i < a.size; i++)
     output << a.ptr[i] << ' ';
     return output;
}

test_array.cpp
#include <iostream>
using std::cout;
#include "array.h"

void outputArray(const Array&);

int main()
{
     Array integers(7);
     outputArray(integers);
     outputArray(15);
     return 0;
}

void outputArray(const Array &arrayToOutput)
{
     cout << "\nA tömb tartalma:\n" << arrayToOutput << "\n\n";
}

A kimenet:
Konstruktor: 7
A tömb tartalma:
0 0 0 0 0 0 0

Konstruktor: 15
A tömb tartalma:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Az Array osztály int paraméterű konstruktorát átalakító operátorként is lehet használni, int -> Array átalakításokra. A kiírásra z outputArray() függvényt használtuk, melynek paramétere const Array referencia. A főprogramban az integers egy Array típusú objektum, melynek deklarálásakor azonnal lefut a konstruktor. Az outputArray(integers) utasítás csupán kiírja a tömb tartalmát a felhasználva a << operátort túlterhelő függvényt. Az outputArray(15) utasítás egész típussal hívja meg az outputArray() függvényt, ami nem int típusú paramétert vár, ezért átalakítja azt Array típussá. Ez egy olyan eset, amikor a konstruktort átalakító operátorként használtuk, és ez sokszor bezavarhat a program logikájába. A C++ az ilyen esetekre tartja fent az explicit kulcsszót, amelyet ha a konstruktor prototípusa elé írunk az osztály definíciójában, akkor kizárja annak implicit átalakító viselkedését. Ha a fenti programban átjavítjuk a definíciót explicit Array(int = 10)-re, akkor a fordító nem fogja engedni az outputArray(15) utasítás használatát. Helyette az outputArray(Array(15)) lesz a helyes.

Mutable tagváltozók

      A mutable kulcsszó egy alternatíva a const_cast-ra, mellyel módosítani lehet a tagváltozókat a const tagfüggvényekben. Egy mutable tagváltozó változtatható marad a const tagfüggvényekben és const objektumokban is. A const_cast és a mutable különböző kontextusban teszik lehetővé a változtatást. Egy olyan const objektum esetén, melynek egyetlen mutable tagváltozója sincs, a const_cast operátort mindig alkalmazni kell valahányszor változtatni szeretnénk egy tagváltozón. Ez a mechanizmus jelentősen csökkenti az olyan tagváltozók véletlen módosítását, amelyek amúgy módosíthatatlanok. Az olyan műveletek, amelyek igénybe veszik a const_cast operátort általában el vannak rejtve a tagfüggvényekben.

#include <iostream>
using std::cout;

class TestMutable
{
     public:
           TestMutable(int v = 0) {value = v;}
           void modifyValue() const {value++;}
           int getValue() const {return value;}
     private:
           mutable int value;
};

int main()
{
     const TestMutable t(99);
     cout << "Kezdeti érték: " << t.getValue();
     t.modifyValue(); //módosítja a mutable változót
     cout << "\nMódosított érték: " << t.getValue();
     return 0;
}

A kimenet:
Kezdeti érték: 99
Módosított érték: 100

A TestMutable osztály egy konstruktort, két const tagfüggvényt és egy mutable tagváltozót tartalmaz. A modifyValue tagfüggvény az amelyik módosítja a tagváltozót. Normális esetben a const tagfüggvények nem kéne tudják módosítani a tagváltozókat, hacsak az objektum ami felett dolgozik nincs a const_cast operátor hatása alatt. Mivel a value tagváltozó mutable, a const tagfüggvény módosítani tudja annak értékét 99-ről 100-ra.

Nincsenek megjegyzések:

Megjegyzés küldése