Mimo, że „Tydzień z Adą” już się skończył, temat tego języka będzie się jeszcze przez jakiś czas przewijał. Jako, że w internecie dosyć trudno znaleźć informacje nawet na temat wydawałoby się dosyć podstawowych konstrukcji języka, będę tutaj opisywał to, co udało mi się odkryć. Dzisiaj na tapecie atrybut limited, który dodany do typu uniemożliwia jego kopiowanie.

C++ i Rule of Five

Czasami mamy do czynienia z pewnymi typami danych, których nie chcemy kopiować i przypisywać do nowych zmiennych. Typowym przykładem jest tu linkowana lista. Struktura danych przechowująca stan listy zawiera zwykle wskaźniki do obiektów zaalokowanych na heapie i jeśli je skopiujemy, a następnie wykonamy na liście jakieś operacje – jedna z kopii może zawierać nieaktualny stan. Innym przykładem bliższym systemom embedded może być struktura danych opisująca jakiś układ sprzętowy np. UART. Taka struktura jest związana z konkretnymi rejestrami sprzętowymi i stanem hardware’owym. Dlatego jeśli ją skopiujemy możemy wprowadzić peryferium w błędny stan.

Aby się przed tym bronić w C++ istnieją copy constructor, move constructor, copy assignment operator i move assignment operator. Aby kontrolować kopiowanie obiektów musimy dla danej klasy napisać własne implementacje tych konstruktorów i operatorów. Istnieje nawet zasada „Rule of three”, w C++11 zmieniona na „Rule of five” po dodaniu operacji move. Mówi ona, że jeśli implementujemy jeden z nich, musimy zaimplementować wszystkie. Bardzo często chcemy po prostu zabronić operacji kopiowania usuwając domyślną implementację i zezwalając jedynie na operację move. Dodatkowo powinniśmy pomyśleć, czy nie potrzebujemy destruktora innego niż domyślny. Deklaracja klasy zachowującej Rule of Five i nie zezwalającej na kopiowanie może wyglądać tak:

class Example
{
	public:
		Example(); //constructor
		
		~Example() = default; //destructor
		
		Example(const Example& other) = delete; //no copy constructor
		Example& operator=(const Example& other) = delete; //no copy assignment
		
		Example(Example&& other) = default; //move constructor
		Example& operator=(Example&& other) = default; //move assignment
};

Doświadczonemu programiście C++ pisanie klas zgodnych z „Rule of Five” pewnie dawno weszło w nawyk, pomagają też odpowiednie IDE. Jednak bazowanie na dodatkowych narzędziach albo dyscyplinie zawsze pozostawia miejsce na błąd. Dlatego w Adzie podeszli do tematu nieco inaczej.

Ada i typy limited

W Adzie możemy zadeklarować strukturę danych jako limited:

   type Example is limited record
      Data : Integer;
   end record;

Deklaracja typu w części public pliku .ads będzie natomiast wyglądała tak:

type Example is limited private;

Taka deklaracja oznacza, że dla danego typu nie są możliwe operacje przypisania (:=) i porównania (=). Próby wykonania tych operacji zwrócą nam błąd kompilacji. Widzimy tutaj, że dodanie do definicji struktury danych słówka limited daje nam podobną funkcjonalność, co w C++ napisanie dużo większej ilości kodu. Oczywiście mechanizm z C++ jest dużo bardziej rozbudowany. Możemy używać operatora move do przenoszenia obiektu na inne zmienne. W Adzie w tym celu musimy skorzystać ze wskaźników. Wersja C++ pozwala nam również pisać własne implementacje konstruktorów inne niż domyślne. W Adzie też możemy to robić. Jednak słowo kluczowe limited obsługuje od razu ten przypadek, kiedy nie chcemy zezwolić na kopiowanie.

Interfejsy limited

W Adzie mamy mechanizm interfejsów podobny jak w Javie. Można go też osiągnąć w C++ deklarując klasę jako składającą się z samych wirtualnych metod bez implementacji. Interfejsy w Adzie również mogą być limited. Oto definicja takiego interfejsu wraz z kilkoma metodami:

   type IExample is limited interface;
   
   procedure Init(This : in out IExample) is abstract;
   procedure ValSet(This : in out IExample; Val : in Integer) is abstract;
   function ValGet(This : in out IExample) return Integer is abstract;

Typy implementujące ten interfejs nie dziedziczą po nim atrybutu limited. Jeżeli chcemy, żeby nasz typ był limited, musimy go takim zadeklarować:

	type ConcreteExample is limited new IExample with private;

private
	type ConcreteExample is limited new IExample with record
		Data : Integer;
	end record;

Konkretne typy nie dziedziczą po interfejsie atrybutu limited, ponieważ możliwe jest dziedziczenie po wielu interfejsach. Jeżeli choć jeden z nich nie jest limited, wtedy cały typ również nie może być.

Więcej na temat interfejsów i atrybutu limited w Adzie znajdziecie w dokumentacji języka (Interfejsy, Typy limited).