Historia pytania:
C++ został zbudowany na bazie C, który dostarczył niewielką liczbę typów wbudowanych: liczby, znaki, tablice. W miarę rozwoju języka pojawiły się takie pojęcia, jak struktury, klasy, enumeracje — stały się one typami zdefiniowanymi przez użytkownika.
Problem:
Typ danych w programie określa, ile pamięci zajmie zmienna, jak będzie inicjowana, kopiowana, niszczona, porównywana. Typy wbudowane mają zachowanie określone przez standard, typy zdefiniowane przez użytkownika — wymagają wyraźnego opisu wszystkich aspektów. Błędy w zarządzaniu typami zdefiniowanymi przez użytkownika mogą prowadzić do awarii, wycieków, nieuczciwych porównań obiektów itp.
Rozwiązanie:
Typy wbudowane to int, float, double, char, bool i inne "prymitywy". Typy zdefiniowane przez użytkownika to wszelkie struktury, klasy, unie stworzone przez ciebie. Dla złożonych zadań należy wprowadzać typy zdefiniowane przez użytkownika z poprawną semantyką kopiowania, porównywania, zarządzania zasobami.
Przykład kodu:
// Typ wbudowany int x = 5; // Typ zdefiniowany przez użytkownika struct Point { double x, y; }; Point a = {1.0, 2.0}; // Klasa z zasobami class MyFile { public: MyFile(const std::string& fn) : f(fopen(fn.c_str(), "r")) {} ~MyFile() { if (f) fclose(f); } private: FILE* f; };
Czy typy zdefiniowane przez użytkownika mogą zachowywać się całkowicie jak wbudowane (np. porównywać się za pomocą == "od ręki")?
Nie, porównanie == działa domyślnie dopiero od C++20 przez defaulted operator==, wcześniej wymagało to wyraźnej definicji.
Czy można nie implementować konstruktora kopiującego i destruktora, jeśli klasa trzyma tylko surowy wskaźnik (int, FILE, itd.)?**
Nie, w takim przypadku domyślne kopiowanie będzie "płytkie", co prowadzi do wycieków lub podwójnego zwolnienia pamięci/zasobu. Należy implementować "Zasadę Piątki".
Czy wartość struktury domyślnie zostanie zainicjowana zerami (Point p;)?
Nie, lokalna struktura może nie być zainicjowana, zawartość pamięci jest losowa. Użyj jawnej inicjalizacji.
Negatywny przypadek:
Klasa opakowująca plik nie zrealizowała destruktora. Program działał, dopóki nie zaczął przetwarzać tysięcy plików bez zamykania deskryptorów.
Plusy: mniej kodu, uproszczony wygląd Minusy: wycieki zasobów, niestabilne zachowanie
Pozytywny przypadek:
Klasa implementuje RAII — plik otwierany jest w konstruktorze, zamykany w destruktorze, kopiowanie jest zabronione.
Plusy: niezawodność, bezpieczeństwo, czysty interfejs Minusy: konieczność pamiętania o "zasadzie pięciu" i napisania większej ilości kodu