Zapoznać się z regułami przedstawionymi w dokumentacji na stacku Zasada zera, zasada pięciu, zasada trzech.
Reguła 3 była stosowana dla C++03 przed wprowadzeniem referencji do r-wartości i obejmowała funkcje: 1, 3, 5. Reguła 5 mówi, że jeśli ktoś potrzebuje przedefiniować jedną z tych metod to najprawdopodobniej musi to zrobić z wszystkimi 5.
Metody te są wywoływane w trakcie życia obiektu w celu oddelegowania odpowiedzialności za zarządzanie zasobami do samego obiektu. Są to operacje konstruowania obiektu jeszcze nie istniejącego na podstawie innego (już zainicjalizowanego), przypisywanie innego obiektu do istniejącego gdy oba już są zainicjalizowane lub niszczenie obiektu.
Przykład:
W przypadku kopiowania stary obiekt zostaje w nienaruszonym stanie (był zawsze przekazywany jako const &). Jeśli dokonujemy przenoszenia stary obiekt jest niszczony, a jego stan jest przenoszony do nowej lokalizacji, czyli liczba zasobów trzymanych w obiektach pozostaje stała…
Przykład:
Zasoby to wszystko co musi być najpierw zainicjalizowane, a następnie zniczone, ale nie przy użyciu konstruktora i destruktora. Np. pamięć jako surowy wskaźnik (inicjalizacja new, zwalnianie delete), uchwyt do pliku (open, close), połączenie z bazą danych, itp…
Jeśli obiekt nie posiada żadnych zasobów tylko składa się z typów prostych i kontenerów biblitecznych to można wykorzystać regułę 0 i polegać na powyższych funkcjach automatycznie wygenerowanych przez kompilator.
Jeśli jednak klasa XXX wygląda tak:
Brak destruktora powoduje wyciek pamięci, która nigdy nie jest zwalniana:
Brak konstruktora może teraz doprowadzić do tragedii wielokrotnej próby zwolnienia tej samej pamięci:
Więc:
I ostatnia poprawka:
Usuwanie domyślnie generowanych funkcji w C++11 jest wyjątkowo prostym zadaniem wystarczy zadeklarować metodę a następnie przypisać jest delete.
W tym momencie nie będzie możliwe przenoszenie obiektów klasy Y. W przypadku zwracania obiektów Y z funkcji kompilator zrezygnuje z optymalizacji przeniesienia i zastosuje kopiujące odpowiedniki metod.
Istnieje jeszcze specjalny typ konstruktora przyjmujący listę inicjalizacyjną, przydatny przy inicjalizacji własnych kontenerów.
Teraz można zainicjalizować obiekt Counter jako:
Słowo kluczowe const ma różne znaczenie w zależności od kontekstu w jakim jest stosowane.
W odniesieniu do metod klasy oznacza, że dana metoda nie może modyfikować elementów składowych klasy jak również nie może wywoływać innych metod niż te zdeklarowane jako const. Jeśli można, warto zadeklarować metodę jako const. Umożliwi to poprawne korzystanie z obiektów tej klasy zadeklarowanych jako stałe.
W odniesieniu do zmiennych i obiektów i innych zmiennych, oznaczają że nie można modyfikować zmiennej/obiektu. Innymi słowy nie można wywoływać na rzecz danego obiektu innych metod niż zadeklarowane jako const
Funkcja i klasy zaprzyjaźnione mają nieograniczony dostęp do wszystkich pól i metod klasy której są przyjaciółmi.
Funkcja zaprzyjaźniona definiowana jest poza zasięgiem klasy, ale informacja o tym że jest ona przyjacielem musi znaleźć się w klasie:
//Plkik Matrix.h class Matrix{ double** data; ... friend void wyzeruj(Matrix& m); ... }; // Plik main.cpp // Funkcja może modyfikować prywatne dane klasy // Matrix, ponieważ została określona jako friend void wyzeruj(Matrix& matrix){ for(int r = 0; r < matrix.rows; r++) for(int c = 0; c < matrix.cols; c++) matrix.data[r][c] = 0; }
Analogicznie do funkcji klasy zaprzyjaźnione maja nieograniczony dostęp d wszystkich pól i metod klasy które są przyjaciłmi. Jeśli klasa Node ma być przyjacielem klasy Lista, to w tej drugiej należy umieścić następującą deklarację:
class Lista{ ... friend class Node; ... };
Słowo kluczowe static może być stosowane zarówno w stosunku do pól klasy jak i jej metod, ale w obu przypadkach ma nieco inne znaczenie.
Pola statyczne są swego rodzaju zmiennymi globalnymi, należącymi jednak do zasięgu klasy. Zmienna statyczna jest współdzielona przez wszystkie obiekty klasy. Do zmiennych statycznych można odwoływać się nie mając utworzonego obiektu.
Słowo kluczowe static dodaje się tylko i wyłącznie podczas deklaracji zmiennej. Użycie słowa kluczowego static podczas inicjalizacji zmiennej powoduje błąd składniowy.
Deklaracja zmiennej statycznej w pliku nagłówkowym:
class Matrix{ public: static int licznik; Matrix(){licznik++;} ~Matrix(){licznik--;} };
Zmienna statyczna musi zostać zainicjalizowana w przestrzeni pliku. Nie można inicjalizować statycznej zmiennej w konstruktorze, lub innej funkcji. Inicjalizacja zmiennej statycznej w pliku cpp:
#include "Matrix.h" int Matrix::licznik = 0; ...
Odwoływanie się do pól statycznych:
int main(){ Matrix m; cout << Matrix::licznik <<endl; cout << Matrix.licznik << endl; }
Metody statyczne można wywoływać bez konieczności tworzenia obiektów klasy. Można z tego wywnioskować, że metody statyczne nie posiadają wskaźnika this.
Nie można zatem wewnątrz metod statycznych wywoływać żadnych innych metod niestatycznych, ani odwoływać się do pól niestatycznych.
Metody statyczne należą jednak do zasięgu klasy i mają dostęp do wszystkich pól klasy:
class Matrix{ ... static void wyswietl(Matrix& m); }; void Matrix::wyswietl(Matrix & m){ for(int r = 0; r < matrix.rows; r++){ for(int c = 0; c < matrix.cols; c++) cout << matrix.data[r][c] << endl; cout << endl; } }
4.5i6
Co oznacza w zapisie matematycznym
4.5 + 6i
Matrix m("[1i3 2i5 3; 3 4 5; 6 7 8]");
Matrix m("[1 2 3;3 4 5; 2 3 4]"); Matrix m2("[3 2 1; 5 4 3; 7 6 5]"); Matrix wynik = m.add(m2);
#include <iostream> #include "Matrix.h" int main(int argc, char* argv[]){ Matrix m1(argv[1]); Matrix m2(argv[2]); cout << "Macierz pierwsza: " << m1.print() << endl; cour << "Macierz druga: " << m2.print() << endl; cout << "Dodawanie" << (m1.add(m2)).print() << endl; cout << "Odejmowanie" << (m1.sub(m2)).print() << endl; cout << "Mnożenie" << (m1.mul(m2)).print() << endl; cout << "Dzielenie" << (m1.div(m2)).print() << endl; cout << "Potęgowanie" << (m1.pow(2)).print() << endl; cout << "Potęgowanie" << (m2.pow(2)).print() << endl; }
Zabezpieczenie programu przed sytuacjami wyjątkowymi w „podstawowej” wersji, wymaganej na laboratorium, związane jest ze sprawdzeniem odpowiedniej wartości i wypisaniem komunikatu dla użytkownika. Bardziej profesjonalny sposób obsługi takich sytuacji związany jest z mechanizmem wyjątków. Więcej o nich dowiemy się na laboratorium Wyjątki.