Kacper Kołodziej

C++11: Funkcje usunięte (= delete)

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);.

Back to archive