Podręcznik
3. Dziedziczenie i polimorfizm
3.3. Abstrakcje / interfejsy
Metoda zadeklarowana w klasie jako wirtualna, która nie ma definicji – jest metodą abstrakcyjną. W C++ oznaczamy ten fakt pisząc =0 po deklaracji metody. Klasa która ma choć jedną metodę abstrakcyjną – jest klasą abstrakcyjną. Nie można tworzyć obiektów typu klas abstrakcyjnych. Klasa która ma wszystkie metody abstrakcyjne i nie posiada pól – jest interfejsem.
W praktyce stosuje się często jako interfejsy klasy, które zawierają kilka metod z implementacjami. Jednakże, te metody, które mają być częścią interfejsu, powinny być oznaczone jako czysto wirtualne. Dodatkowo - by być zgodnym z semantycznym znaczeniem interfejsu - klasa taka nie powinna mieć pól.
class CFiguraBaza {
public:
virtual void rysuj() = 0;
};
class CFigura : public CFiguraBaza {
public:
void przesun(int _x, int _y) {
m_x = _x;
m_y = _y;
rysuj();
};
private:
int m_x;
int m_y;
};
class CKolo :public CFigura {
public:
virtual void rysuj() {
// kod rysowania
}
};
Zmodyfikowany przykład dziedziczenia figur, z uwzględnioną koncepcją interfejsu możecie zobaczyć poniżej:

Wprowadzenie tutaj koncepcji interfejsów ściśle wiąże się z pojęciem typów abstrakcyjnych. Typy abstrakcyjne w założeniu mają izolować użytkownika od ich implementacji. Z typów abstrakcyjnych korzystamy tylko przez referencje / wskaźniki. Nie można tworzyć bezpośrednio obiektów typów abstrakcyjnych - próba utworzenia instancji ICFigura albo CFiguraBaza zakończy się błędem kompilacji. Typy takie możemy tworzyć jedynie jako typy konkretne - i potem przekazywać interfejsy do nich.
Typowa klasa abstrakcyjna zazwyczaj nie ma konstruktora - bo i tak nie ma pól do inicjowania. Natomiast prawie zawsze powinna mieć wirtualny destruktor - dzięki czemu kasowanie obiektu przy wykorzystaniu wskaźnika na klasę bazową i tak spowoduje wywołanie odpowiedniej postaci destruktora.
Nie da się także ich prosto kopiować ani klonować.
Klonowanie obiektów
W przypadku obiektów będących w hierarchii dziedziczenia, kopiowanie ich nie da się prosto wykonać korzystając jedynie z mechanizmów polimorfizmu – przecież konstruktor nie jest wirtualny i nie jest dziedziczony. Dlatego też rozwiązanie poniżej raczej nie zadziała:
class A {
public:
A() {};
A(const A& _s) { };
virtual void f() {
cout<<"A nadaje\n";
}
};
class B : public A {
public:
B() {};
B(const B& _s) : A(_s) { };
virtual void f() {
cout<<"B nadaje\n";
}
};
int main(int argc, char *argv[])
{
B *b = new B;
A *a1 = b;
A *a2 = new A(*a1);
//A *a3 = new B(*a1);
a1->f();
a2->f();
}
Możecie sami się przekonać, uruchamiając powyższy przykład. Mimo że a2 zostało utworzone jako kopia a1 – które jest typu B, to i tak wywołany został konstruktor A, a nie B. Jeśli znamy typ obiektu pochodnego przed kopiowaniem – to możemy jawnie wywołać konstruktor B. Ale przecież cała zabawa z polimorfizmem polega na tym, by móc korzystać z obiektu jedynie w oparciu o jego interfejs – a więc nie znając docelowego typu obiektu. Czy da się kopiować elementy w taki sposób? Da ... wystarczy odpowiednio wykorzystać mechanizm funkcji wirtualnych.
class A {
public:
A() { };
A(const A& _s) {};
virtual void f() { cout<<"A " << FMyNum << " nadaje\n"; }
virtual A* clone() { return new A(*this); };
protected:
static int m_cnt;
int m_myNum;
};
int A::m_cnt = 0;
class B : public A {
public:
B() {};
B(const B& _s) : A(_s) { };
virtual void f() { cout<<"B " << FMyNum << " nadaje\n"; }
virtual A* clone() { return new B(*this); };
};
int main(int argc, char *argv[])
{
B *b = new B;
A *a1 = b;
A *a2 = new A(*a1);
A *a4 = a1->clone();
a1->f();
a2->f();
a4->f();
}
Kod powyżej jest jeszcze uzupełniony o dodatkową informację – wykorzystując pole statyczne, zliczamy wszystkie egzemplarze danej klasy.
Ogólnie - z pojęciem tworzenia instancji typów abstrakcyjnych jest związane kilka wzorców programistycznych, z fabryką abstrakcyjną na czele.