Posts tagged ‘C#’

Domykanie klas i metod

2007-09-09 12:10

W teorii dziedziczenie to niezbyt skomplikowana relacja między klasami, ale w praktyce potrzeba czasem niestandardowych rozwiązań. Niekiedy bowiem możliwość rozszerzenia hierarchii klas w nieskończoność nie jest wcale pożądana. Zazwyczaj chodzi tu o względy projektowe, rzadziej o efektywność.
Pytanie brzmi: czy można jakoś zabezpieczyć konkretną klasę przed dziedziczeniem (czyli zabronić w tworzenia opartych o nią klas pochodnych)?

W C# i Javie jest to możliwe za pomocą specjalnych słów kluczowych – odpowiednio sealed (‘zapieczętowany’) i final. Wystarczy opatrzyć nimi definicję klasy, by kompilator odmówił skompilowania kodu, który wykorzystywałby taką klasę jako bazową:

  1. public sealed class Foo { };
  2.  
  3. // błąd - dziedziczenie zapieczętowanej klasy
  4. public class Bar : Foo { };

Modyfikatory sealed/final można też stosować do metod wirtualnych. W tym przypadku sprawiają one, że dana metoda wirtualna nie może być nadpisywana w klasach pochodnych. Chwilowo nie przychodzi mi do głowy żaden pomysł na pokazanie przydatności takiego triku, ale zapewne takowy przykład istnieje :)

W C++ nie mamy rzecz jasna wspomnianych słów kluczowych (ani żadnych, które działałyby podobnie), ale istnieje sposób na zabezpieczenie klasy przed dziedziczeniem. Jest on jednak brzydki. A nawet bardzo brzydki. Mimo to zaprezentuję go.
Jak to często bywa w takich sytuacjach, sztuczka polega na kreatywnym wykorzystaniu pewnego mechanizmu językowego unikalnego dla C++, który generalnie służy do czegoś zupełnie innego. Tutaj chodzi o dziedziczenie wirtualne – a dokładniej o to, że wirtualna klasa bazowa musi być bezpośrednio inicjalizowana przez każdą klasę pochodną; nawet tą w setnym pokoleniu niżej. Do tego dodać należy jeszcze prywatne konstruktory i deklarację przyjaźni, a następnie zamieszać i doprawić do smaku, by ostatecznie upichcić coś w tym stylu:

  1. class CFoo; // deklaracja klasy, którą chcemy zabezpieczyć przed dziedziczeniem
  2.  
  3. // sztuczna klasa pomocnicza - "blokada"
  4. class CFoo_Lock
  5. {
  6.    // deklaracja przyjaźni
  7.    friend class CFoo;
  8.  
  9.    private:
  10.       // prywatne konstruktory
  11.       CFoo_Lock() { }
  12.       CFoo_Lock(const CFoo_Lock&) { }
  13. };
  14.  
  15. // zapieczętowana klasa
  16. class CFoo : public virtual CFoo_Lock { /* ... */ };
  17.  
  18. // próba dziedziczenia - błąd: brak dostępu do konstruktora CFoo_Lock
  19. class CBar : public CFoo { /* ... */ };

Klasa pomocnicza CFoo_Lock ma prywatny konstruktor domyślny, więc generalnie nie może być on wywoływany. W szczególności nie może być on wywołany przez konstruktor CBar – a jest to konieczne, gdyż CFoo_Lock jest wirtualną klasą bazową i musi być zainicjalizowana bezpośrednio przez dowolną klasę pochodną (nawet niebezpośrednio pochodną). Dlatego też próba dziedziczenia CFoo skończy się błędem mówiącym o braku dostępu do prywatnego konstruktora CFoo_Lock.
Dlaczego zatem CFoo działa? To już zasługa deklaracji przyjaźni umieszczonej w klasie-blokadzie. Przyjaźń międzyklasowa nie jest jednak dziedziczna, więc nasza blokada odmawia dostępu do prywatnych składowych (czyli konstruktorów) klasom pochodnym CFoo. Dzięki temu (i niuansom dziedziczenia wirtualnego) cały ten trik działa.

To rozwiązanie można naturalnie uczynić bardziej automatycznym – wystarczy zaprząc do pracy szablony i preprocesor, by uzyskać prawie to samo co sealed/final w C#/Javie. Trudno będzie jednak uznać to kombinowanie za eleganckie.
Wygląda więc na to, że najlepszym sposobem na zabezpieczenie klasy przed dziedziczeniem w C++, to po prostu… pokazać na nią palcem (tj. komentarzem) i powiedzieć “Nie dziedzicz mnie!”. No i mieć nadzieję, że ktoś nas posłucha.

Tags: , , ,
Author: Xion, posted under Programming » 1 comment
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.