Programowanie obiektowe w C++ Wykład 12 dr Lidia Stępień Akademia im. Jana Długosza w Częstochowie L. Stępień (AJD) 1 / 22
Zakresowe pętle for double tab[5] {1.12,2.23,3.33,4.12,5.22 for(double x: tab) cout << x << ", "; double tab[5] {1.12,2.23,3.33,4.12,5.22 for(double &x: tab) x = x * 2; vector<int> A(5); for(auto &a: A) a = rand()%26; for(auto a: A) cout << a << ", "; L. Stępień (AJD) 2 / 22
Klasyczna referencja Klasyczna referencja (referencja do l-wartości) jest wyrażeniem reprezentującym dane, dla których da się ustalić adres w pamięci. int n; int *pn = new int; const int b = 123; int &rn = n; const int &rb = b; int &rpn = *pn; Pierwotne znaczenie l-wartości wiąże się z możliwością występowania po lewej stronie operatora przypisania. Wyjątkiem jest klasa wyrażeń związana z modyfikatorem const, które nie mogą występować po lewej stronie operatora przypisania, ale są adresowalne i są l-wartościami. L. Stępień (AJD) 3 / 22
Referencje r-wartościowe C + +11 wprowadza referencję r-wartościową (oznaczoną symbolem &&). Można je wiązać z r-wartościami,tzn. litarałami stałych (poza literałem napisowym), wyrażeniami w rodzaju x + y, czy funkcjami zwracającymi wartości, ale nie zwracającymi referencji. int x = 10; int y = 20; int && r1 = 13; int && r2 = x + y; int && r3 = sqrt(4.0); UWAGI: r2 - związana jest z obliczoną wartością wyrażenia x + y w momencie tworzenia referencji. Późniejsze zmiany x i y nie zmieniają wartości r2. Związanie r-wartości w referencji r-wartościowej oznacza przechowanie tej wartości w miejscu, dla którego da się określić adres. L. Stępień (AJD) 4 / 22
Przykład 1 # include<iostream> using namespace std; inline int F(char z) {return (int) z; int main(){ int x = 10; int y = 20; int && r1 = 13; int && r2 = x + y; int && r3 = sqrt(4.0); int && r4 = F( a ); cout << "wartosc: " << r1 << " adres: " << &r1 << endl; cout << "wartosc: " << r2 << " adres: " << &r2 << endl; cout << "wartosc: " << r3 << " adres: " << &r3 << endl; cout << "wartosc: " << r4 << " adres: " << &r4 << endl; L. Stępień (AJD) 5 / 22
Semantyka przeniesienia (ang. move semantics) vector<string> str; // Tworzymy wektor 20000 łańcuchów, każdy po 1000 znaków. vector<string> str_copy(str); vector<string> upper(const vector<string> &vs) { vector<string> temp;... return temp; vector<string> str2(upper(str)); L. Stępień (AJD) 6 / 22
Przykład 2 class X{ private: int n; char *s; static int ile; void Pokaz() const; public: X(); explicit X(int); X(int, char); X(const X&); X(X&&); ~X(); X operator+(const X&) const; void Wypisz() const; ; L. Stępień (AJD) 7 / 22
int X::ile = 0; X::X() { ++ile; n = 0; s = nullptr; cout << "Konstruktor domyslny: " << ile << endl; Pokaz(); X::X(int n) : n(n) { ++ile; cout << Konstruktor (int): " << ile << endl; s = new char[n]; Pokaz(); L. Stępień (AJD) 8 / 22
X::X(int n, char z) : n(n) { ++ile; cout << "Konstruktor (int, char): " << ile << endl; s = new char[n]; for(int i=0; i < n; ++i) s[i] = z; Pokaz(); X::~X(){ cout << "Destruktor: " << --ile <<endl; cout << "Usunieto obiekt" << endl; Pokaz(); delete []s; s = nullptr; L. Stępień (AJD) 9 / 22
X::X(const X &ob) : n(ob.n) { ++ile; cout << "Konstruktor (const X&): " << ile << endl; s = new char[n]; for(int i=0; i < n; ++i) s[i] = ob.s[i]; Pokaz(); X::X(X && ob) : n(ob.n) { ++ile; cout << "Konstruktor (X&&): " << ile << endl; s = ob.s; // przechwycenie adresu ob.s = nullptr; ob.n = 0; Pokaz(); L. Stępień (AJD) 10 / 22
X X::operator+(const X& ob){ X temp = X(n+ob.n); for(int i = 0; i < n; ++i) temp[i] = s[i]; for(int i = n, j=0; i < temp.n; ++i) temp[i] = ob.s[j]; return temp; void X::Pokaz() const{ cout << "n = " << n; cout << ", adres 0-wego el: " << (void*) s << endl; void Wypisz() const{ if(n == 0)cout << "Pusty obiekt" << endl; else for(int i = 0; i < n; ++i)cout << s[i] << ", "; cout <<endl; L. Stępień (AJD) 11 / 22
int main(){ X jeden(10, x ); X dwa = jeden; X trzy(20, a ); X cztery(jeden + trzy); cout << "1: " ; jeden.wypisz(); cout << "2: " ; dwa.wypisz(); cout << "3: " ; trzy.wypisz(); cout << "4: " ; cztery.wypisz(); L. Stępień (AJD) 12 / 22
Wnioski Stosowanie referencji r-wartościowych umożliwia semantykę przeniesienia, ale jeszcze jej nie gwarantuje. X dwa = jeden; X cztery(jeden + trzy); Ponieważ wartością wyrazenia jeden + trzy jest r-wartość, może być argumentem konstruktora przenoszącego tworzacego obiekt cztery. Zatem w klasie musi być zdefiniowany konstruktor przenoszący. Obiekty inicjalizowane l-wartościami są inicjalizowane za pośrednictwem konstruktora kopiującego. Obiekty inicjalizowane r-wartościami są inicjalizowane za pośrednictwem konstruktora przenoszącego. L. Stępień (AJD) 13 / 22
Operator przypisania X& X::operator=(const X& ob) { if (this == &ob) return *this; delete []s; n = ob.n; s = new char[n]; for(int i = 0; i < n; ++i) s[i] = ob.s[i]; return *this; X& X::operator=(X&& ob) { if (this == &ob) return *this; delete []s; n = ob.n; s = ob.s; ob.n = 0; ob.s = nullptr; return *this; L. Stępień (AJD) 14 / 22
Metody domyślne C++11 daje programiście możliwość wymuszenia na kompilatorze syntezy pewnych metod specjalnych (konstruktor domyślny, konstruktor kopiujący, operator przypisania, destruktor, konstruktor przenoszący, przenoszący operator przypisania) poprzez jawne zadeklarowanie tych metod jako wersji domyślnych. class X{ public: X(X&&); X() = default; X(const X &) = default; X& operator=(const X &) = default; ; L. Stępień (AJD) 15 / 22
Metody usunięte C++11 daje programiście możliwość wymuszenia na kompilatorze zablokowania (usuwania) dowolnej metody w tym metod specjalnych. Próba wywołania metody zablokowanej, spowoduje błąd kompilacji. class X{ public: X(X&&); X() = default; X(const X &) = delete; X& operator=(const X &) = delete; X(X&&) = default; X& operator=(x&&) = default; ; L. Stępień (AJD) 16 / 22
Delegowanie konstruktorów C++11 pozwala na użycie istniejącego konstruktora jako elementu składowego definicji innego konstruktora - jest to mechanizm zwany delegowaniem. Do zapisu wykorzystuje się wariantu składni listy inicjalizacyjnej konstruktora, np. class X{ int a; double b; string s; public: X(); X(int); X(int, double); X(int, double, string); ; L. Stępień (AJD) 17 / 22
Delegowanie konstruktorów X::X(int a, double b, string s) : a(a), b(b), c(c) { X::X() : X(0, 0.1, "Ala"){ X::X(int a) : X(a, 0.1, "Ala"){ X::X(int a, double b) : X(a, b, "Ala"){ L. Stępień (AJD) 18 / 22
Dziedziczenie konstruktorów Klasa pochodna może wciągnąć do swojego zasięgu wszystkie konstruktory klasy bazowej poza konstruktorem domyślnym, konstruktorem kopiującym i konstruktorem przenoszącym. Konstruktory klasy bazowej o prototypie identycznym z konstruktorami klasy pochodnej nie będą wykorzystywane. class Baza{ int a; double b; public: Baza(): a(0), b(0) { Baza(int k) : a(k), b(0) { Baza(double b) : a(-1), b(b) { Baza(int k, double b) : a(a), b(b) { void Wypisz() const {cout << a << ", " << b << endl; ; L. Stępień (AJD) 19 / 22
Dziedziczenie konstruktorów class Pochodna{ short c; public: using Baza::Baza; Pochodna() : c(10){ Pochodna(double x) : Baza(2*x), c((int)x){ Pochodna(int a): j(-1), Baza(a, 0.5*a){ void Wypisz() const { cout << j << ", "; Baza::Wypisz(); ; int main(){ Pochodna o1; Pochodna o2(2.12); Pochodna o3(3,1.14); L. Stępień (AJD) 20 / 22
Zarządzanie metodami wirtualnymi: final Specyfikator final blokuje wywołania wirtualne danej metody w kolejnych klasach pochodnych. Uniemożliwia to zmianę definicji metody z klasy bazowej w klasach pochodnych. virtual void F(char c) const final { cout << c << endl; L. Stępień (AJD) 21 / 22