Ten artyku艂 pochodzi z mojego starego bloga na kacperkolodziej.pl.
Dzisiaj postanowi艂em napisa膰 kr贸tko o usuwaniu z klas funkcji, kt贸re s膮 domy艣lnie definiowane (i deklarowane) przez kompilator. Powodem tego jest fakt i偶 w czasie prac nad bibliotek膮 Tamandua, brak usuni臋cia domy艣lnie definiowanego konstruktora kopiuj膮cego spowodowa艂 bardzo trudny do znalezienia b艂膮d. Poniewa偶 jego zlokalizowanie kosztowa艂o mnie oko艂o godziny intensywnej pracy z GNU debuggerem, postanowi艂em, 偶e od dzisiaj b臋d臋 o takie niuanse dba艂 zaraz po stworzeniu nowej klasy.
Na pocz膮tek stw贸rzymy przyk艂adow膮 klas臋, kt贸ra nie b臋dzie mia艂a jawnie zdefiniowanych konstruktor贸w.
class A {
private:
int n;
public:
int f();
};
Kompilator w przypadku nienapotkania nast臋puj膮cych funkcji w deklaracji klasy, zadeklaruje i zdefiniuje je sam:
- domy艣lny konstruktor:
A::A(); - konstruktor kopiuj膮cy:
A::A(const A&); - konstruktor przenosz膮cy:
A::A(A&&); - operator przypisania kopiuj膮cy:
A::operator=(const A&); - operator przypisania przenosz膮cy:
A::operator=(A&&);
Kilka s艂贸w o konstruktorach
Dla os贸b, kt贸re stawiaj膮 swoje pierwsze kroki w C++ niekt贸re z podanych wy偶ej funkcji mog膮 wygl膮da膰 tajemniczo. W tej cz臋艣ci artyku艂u opisz臋 kr贸tko ka偶dy z nich.
- Domy艣lny konstruktor jest wywo艂ywany przy tworzeniu nowego obiektu danej klasy. Jak wiadomo, mo偶emy zdefiniowa膰 swoje konstruktory przyjmuj膮ce r贸偶ne argumenty, ale ten domy艣lny jest definiowany automatycznie w przypadku gdy sami nie zdefiniujemy 偶adnego konstruktora.
- Konstruktor kopiuj膮cy s艂u偶y do
skopiowania sk艂adowych z innego obiektu tego samego typu. Przyk艂adem wywo艂ania:
A e1(); A e2(e1); // wywo艂anie konstruktora kopiuj膮cego - Konstruktor przenosz膮cy wykorzystuje tzw. "rvalue references" (referencje do r-warto艣ci). Jest to nowo艣膰 wprowadzona w C++11 razem z konstruktorami przenosz膮cymi.
- Operatory przypisania pozwalaj膮 nam na korzystanie z nast臋puj膮cej sk艂adni:
A e1; A e2 = e1;W tym przypadku zostanie wywo艂any operator przypisania kopiuj膮cy, poniewa偶 obiekt e1 istnieje ju偶 w pami臋ci. - W przypadku tego kodu:
A e1 = A();zostanie wywo艂any operator przypisania przenosz膮cy, poniewa偶A()zostanie zinterpretowane jako r-warto艣膰.
R-warto艣ciom i przenoszeniu zosta艂 po艣wi臋cony osobny artyku艂.
Funkcje usuni臋te
Przejd藕my teraz do meritum. Je偶eli chcemy aby obiekt贸w naszej klasy nie mo偶na
by艂o kopiowa膰, wystarczy 偶e sami zdefiniujemy je w ciele klasy w odpowiedni
spos贸b. W standardach < C++11 konieczne by艂o zdefiniowanie takiego
konstruktora/operatora w przestrzeni prywatnej i nie definiowanie jego cia艂a.
Wtedy, w przypadku pr贸by skopiowania obiektu nawet wewn膮trz kt贸rej艣 z funkcji
sk艂adowych (czyli uprawnionych do korzystania z prywatnych sk艂adowych)
kompilator zwr贸ci b艂膮d undefined reference (konstruktor/operator b臋dzie
zadeklarowany, ale nie zdefiniowany - nie b臋dzie czego wywo艂a膰). C++11
dostarcza nam o wiele wygodniejszego rozwi膮zania. Deklaracj臋 funkcji
przyr贸wnujemy do s艂owa kluczowego delete. Kompilator nie b臋dzie wtedy
automatycznie definiowa艂 takiej funkcji, a w przypadku pr贸by u偶ycia w kodzie
zaprotestuje jasnym komunikatem informuj膮cym o fakcie skasowania funkcji przez
tw贸rc臋 klasy.
Kompilator g++-4.7 przy pr贸bie u偶ycia usuni臋tego konstruktora kopiuj膮cego zwr贸ci艂 nast臋puj膮ce b艂臋dy:
delete.cpp: In function 'int main(int, char**)':
delete.cpp:16:9: error: use of deleted function 'A::A(const A&)'
delete.cpp:10:3: error: declared here
delete.cpp:17:9: error: use of deleted function 'A::A(const A&)'
delete.cpp:10:3: error: declared here
Prosz臋 zwr贸ci膰 uwag臋, 偶e w wierszu 17 u偶y艂em operatora przypisania kopiuj膮cego,
kt贸ry wywo艂uje konstruktor kopiuj膮cy. Usuni臋cie konstruktora kopiuj膮cego
automatycznie uniemo偶liwia r贸wnie偶 korzystanie z operatora przypisania
kopiuj膮cego. Analogiczna sytuacja jest w przypadku konstruktora i operatora
przenosz膮cego. Je偶eli natomiast usun臋 tylko operator przypisania kopiuj膮cy to
kompilator g++ skompiluje kod -- po prostu automatycznie zamieni A e3 = e1;
na A e3(e1);.