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 stosie
Zauważ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