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