Posted on

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:

  1. domy艣lny konstruktor: A::A();
  2. konstruktor kopiuj膮cy: A::A(const A&);
  3. konstruktor przenosz膮cy: A::A(A&&);
  4. operator przypisania kopiuj膮cy: A::operator=(const A&);
  5. 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.

  1. 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.
  2. 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
  3. Konstruktor przenosz膮cy wykorzystuje tzw. "rvalue references" (referencje do r-warto艣ci). Jest to nowo艣膰 wprowadzona w C++11 razem z konstruktorami przenosz膮cymi.
  4. 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.
  5. 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);.