W C++ (podobnie jak w Javie) można definiować klasy abstrakcyjne. Klasami takimi będą klasy, w których pewne metody w ogóle nie są zdefiniowane, a tylko zadeklarowane. Takie metody powinny być oczywiście wirtualne — w dziedziczących klasach muszą być dostarczone konkretne implementacje tych metod, aby można było tworzyć ich obiekty. Obiektów samej klasy abstrakcyjnej tworzyć nie można, bo nie jest to klasa do końca zdefiniowana. Zazwyczaj służy tylko jako definicja interfejsu, czyli zbioru metod jakie chcemy implementować na różne sposoby w klasach dziedziczących.
Można natomiast tworzyć obiekty klas pochodnych (nieabstrakcyjnych), w których metody wirtualne zadeklarowane ale nie zdefiniowane w klasie bazowej zostały przesłonięte konkretnymi implementacjami (klasy takie stają się wtedy konkretne). Co bardzo ważne, do takich obiektów można się odnosić poprzez wskaźniki i referencje o typie statycznym abstrakcyjnej klasy bazowej.
Metodę wirtualną można zadeklarować jako czysto wirtualną pisząc po nawiasie kończącym listę argumentów ' =0', na przykład:
virtual void fun(int i) = 0;W ten sposób informujemy kompilator, że definicji tak zadeklarowanej metody może w ogóle nie być, a zatem cała klasa, w której tę metodę zadeklarowano, będzie abstrakcyjna.
W zasadzie, choć rzadko się to robi, metodę czysto wirtualną (albo zerową) można w klasie abstrakcyjnej zdefiniować, ale klasa pozostaje przy tym abstrakcyjna i nie można tworzyć jej obiektów. Tak czy owak, w klasach dziedziczących trzeba tę metodę przedefiniować, aby uczynić te klasy klasami konkretnymi, których obiekty będzie można tworzyć. Do wersji tej metody zdefiniowanej w abstrakcyjnej klasie bazowej odwołać się wtedy można poprzez jawną specyfikację zakresu (' Klasa::fun()').
1. #include <iostream> 2. #include <cmath> // atan 3. using namespace std; 4. 5. class Figura { 6. protected: 7. static const double PI; 8. public: 9. virtual double getPole() = 0; 10. virtual double getObwod() = 0; 11. virtual void info(ostream&) = 0; 12. static double totalPole(Figura* tab[], int size) { 13. double suma = 0; 14. for (int i = 0; i < size; ++i) 15. suma += tab[i]->getPole(); 16. return suma; 17. } 18. static Figura* maxObwod(Figura* tab[], int size) { 19. int ind = 0; 20. for (int i = 0; i < size; ++i) 21. if (tab[i]->getObwod() > 22. tab[ind]->getObwod()) 23. ind = i; 24. return tab[ind]; 25. } 26. }; 27. const double Figura::PI = 4*atan(1.); 28. void Figura::info(ostream& str) { 29. str << "Figura: "; 30. } 31. 32. class Kolo : public Figura { 33. double promien; 34. public: 35. Kolo(double r) : promien(r){ } 36. double getPole() { return PI*promien*promien; } 37. double getObwod() { return 2*PI*promien; } 38. void info(ostream& str) { 39. Figura::info(str); 40. str << "kolo o promieniu " << promien; 41. } 42. }; 43. 44. class Kwadrat : public Figura { 45. double bok; 46. public: 47. Kwadrat(double s) : bok(s) { } 48. double getPole() { return bok*bok; } 49. double getObwod() { return 4*bok; } 50. void info(ostream& str) { 51. Figura::info(str); 52. str << "kwadrat o boku " << bok; 53. } 54. }; 55. 56. int main() { 57. Figura* tab[] = { new Kolo(1.), new Kwadrat(1.), 58. new Kolo(2.), new Kwadrat(3.) 59. }; 60. int size = sizeof(tab)/sizeof(tab[0]); 61. for (int i = 0; i < size; ++i) { 62. tab[i]->info(cout); 63. cout << endl; 64. } 65. Figura* maxobw = Figura::maxObwod(tab,size); 66. cout << "Suma pol: " << Figura::totalPole(tab,size) 67. << "\nFigura o najwiekszym obwodzie: "; 68. maxobw->info(cout); 69. cout << "\n ma obwod " 70. << maxobw->getObwod() << endl; 71. }
Zauważmy też, że metoda info jest zadeklarowana jako czysto wirtualna, chociaż jest zaimplementowana (linie 28-30). Pozostaje jednak czysto wirtualna — wszystkie konkretne klasy dziedziczące muszą ją zaimplemetować. Do implementacji z abstrakcyjnej klasy bazowej mogą się jednak odwołać poprzez jej nazwę kwalifikowaną (linie 39 i 51).
Rozpatrzmy jeszcze jeden przykład klasy czysto abstrakcyjnej: klasa ta
definiuje interfejs do tworzenia stosów i operowaniu na nich.
1. #include <iostream> 2. using namespace std; 3. 4. class STACK 5. { 6. public: 7. virtual void push(int) = 0; 8. virtual int pop() = 0; 9. virtual bool empty() = 0; 10. static STACK* getInstance(int); 11. virtual ~STACK() { } 12. }; 13. 14. class ListStack: public STACK { 15. 16. struct Node { 17. int data; 18. Node* next; 19. Node(int data, Node* next) 20. : data(data), next(next) 21. { } 22. }; 23. 24. Node* head; 25. 26. ListStack() { 27. head = NULL; 28. cerr << "Tworzenie ListStack" << endl; 29. } 30. 31. ListStack(const ListStack&) { } 32. void operator=(ListStack&) { } 33. 34. public: 35. friend STACK* STACK::getInstance(int); 36. 37. int pop() { 38. int data = head->data; 39. Node* temp = head->next; 40. delete head; 41. head = temp; 42. return data; 43. } 44. 45. void push(int data) { 46. head = new Node(data, head); 47. } 48. 49. bool empty() { 50. return head == NULL; 51. } 52. 53. ~ListStack() { 54. cerr << "Usuwanie ListStack" << endl; 55. while (head) { 56. Node* node = head; 57. head = head->next; 58. cerr << " usuwanie wezla" << node->data <<endl; 59. delete node; 60. } 61. } 62. }; 63. 64. class ArrayStack : public STACK { 65. 66. int top; 67. int* arr; 68. enum {MAX_SIZE = 100}; 69. 70. ArrayStack() { 71. top = 0; 72. arr = new int[MAX_SIZE]; 73. cerr << "Tworzenie ArrayStack" << endl; 74. } 75. 76. ArrayStack(const ArrayStack&) { } 77. void operator=(ArrayStack&) { } 78. 79. public: 80. friend STACK* STACK::getInstance(int); 81. 82. void push(int data) { 83. arr[top++] = data; 84. } 85. 86. int pop() { 87. return arr[--top]; 88. } 89. 90. bool empty() { 91. return top == 0; 92. } 93. 94. ~ArrayStack() { 95. cerr << "Usuwanie ArrayStack z " << top 96. << " elementami wciaz na stosie" << endl; 97. delete [] arr; 98. } 99. }; 100. 101. STACK* STACK::getInstance(int size) { 102. if (size > 100) 103. return new ListStack(); 104. else 105. return new ArrayStack(); 106. } 107. 108. int main() { 109. 110. STACK* stack; 111. 112. stack = STACK::getInstance(120); 113. stack->push(1); 114. stack->push(2); 115. stack->push(3); 116. stack->push(4); 117. cerr << "pop " << stack->pop() << endl; 118. cerr << "pop " << stack->pop() << endl; 119. delete stack; 120. 121. stack = STACK::getInstance(50); 122. stack->push(1); 123. stack->push(2); 124. stack->push(3); 125. stack->push(4); 126. cerr << "pop " << stack->pop() << endl; 127. cerr << "pop " << stack->pop() << endl; 128. delete stack; 129. }
Funkcja main jest klientem klasy STACK. Tworzone są tu dwa obiekty o typie statycznym STACK; typ dynamiczny jest w każdym przypadku inny, jak widzimy z wydruku
Tworzenie ListStack pop 4 pop 3 Usuwanie ListStack usuwanie wezla2 usuwanie wezla1 Tworzenie ArrayStack pop 4 pop 3 Usuwanie ArrayStack z 2 elementami wciaz na stosieZauważmy, że oba stosy używane są w analogiczny sposób; w zasadzie klient nie wie, jakiej konkretnej klasy są zwrócone obiekty. Co więcej, nie musi nawet wiedzieć, że są tak naprawdę różnych klas.
Konstruktor kopiujący i operator przypisania zostały w obu klasach zdefiniowane jako prywatne (linie 31-32 i 76-77). Zapobiega to kopiowaniu i przypisywaniu obiektów, co nie miałoby sensu, bo obiekty klas ListStack i ArrayStack są zupełnie różne (mają inne pola, a nawet rozmiary).
T.R. Werner, 21 lutego 2016; 20:17