C++11 #9: Wyłączanie funkcji z użycia: słowo kluczowe delete.

|

Słowo kluczowe delete w deklaracji funkcji.

Czasami zachodzi potrzeba uniemożliwienia wywołania konkretnej funkcji. A dokładniej – zadbania o to, żeby zdefiniowanej wcześniej funkcji nie można było użyć w kodzie importującym/używającym wcześniej zdefiniowanych konstrukcji.

Najciekawsze są sytuacje, w których kompilator samodzielnie jest w stanie wygenerować teoretycznie potrzebne funkcje i użyć ich, co pozbawia nas niejako kontroli nad tym procesem. Przykładem są tutaj choćby funkcje szablonowe, czy też automatycznie generowane konstruktory oraz operatory w klasach.

Jak uniknąć kopiowania obiektu ?

Często zdarza się, że chcemy zabronić kopiowania stworzonego obiektu – najczęściej dlatego, że nie ma to w przypadku konkretnej klasy sensu i może powodować przeróżne błędy. Oznacza to, że w przypadku klas chcemy uniemożliwić wywołanie dwóch funkcji składowych – konkstruktora kopiującego oraz kopiującego operatora przypisania.

Dotychczas (C++98/03) można to było wykonać w taki sposób:

class MyClass
{
public:
    ...
private:
    MyClass(const MyClass&);
    MyClass& operator=(const MyClass&);
}

Czyli de facto deklarujemy konstruktor kopiujący oraz kopiujący operator przypisania jako składowe prywatne. Jednocześnie nigdzie nie definiujemy tych funkcji.

W C++11 możemy to zrobić inaczej – możemy zadeklarować konkretne funkcje jako usunięte (ang. deleted functions). Podobnie, jak w przypadku specyfikatorów override oraz final, należy umieścić słowo kluczowe na końcu definicji funkcji. Różnicą jest tutaj dodatkowy znak przypisania.

Kod wygląda następująco:

class MyClass
{
public:
    MyClass(const MyClass&) = delete;
    MyClass& operator=(const MyClass&) = delete;
    ...
}

Zalety funkcji usuniętych.

Istnieje kilka powodów, dla których słowo kluczowe delete jest preferowanym sposobem wyłączania funkcji z użycia.

Funkcje zdefiniowane w ten sposób nie mogą być użyte w żaden sposób – oznacza to wczesne błędy na etapie kompilacji kodu. W C++98/03 mieliśmy kod, który co prawda nie będzie działał nawet w przypadku użycia go przez funkcje składowe tej samej klasy lub klasę zaprzyjaźnioną (dzięki brakowi definicji), ale błąd zostanie wykryty dopiero na etapie linkowania.

Istnieje też niepisana zasada, aby definiować funckje usunięte jako publiczne. Dzięki temu niejako oszukujemy kompilator, który dostępność funkcji sprawdza przed tym, czy nie jest ona usunięta. Skutkuje to lepszymi informacjami o błędach.

Każda funkcja może zostać zadeklarowana jako usunięta.

Kolejną istotną cechą jest to, że każda (nie tylko składowa) funkcja może być zadeklarowana jako usunięta. Dzięki temu zyskujemy kolejny oręż w naszej nierównej walce z kompilatorem 🙂 Przykładem może być tutaj następujący kod:

bool checkValue(int a_Value);

Załóżmy, że powyższa funckja sprawdza nam wartość podaną jako argument. Jak wiemy, dozwolone są niejawne konwersje do typu całkowitego z całkiem pokaźnego zestawu innych typów. Dlatego taki kod:

bool result1 = checkValue(false);
bool result2 = checkValue('x');

jest jak najbardziej poprawny w sensie semantycznym. W obu przypadkach nastąpi niejawna konwersja argumentu do typu całkowitego (nie ma problemu z konwersją typów bool oraz char do int), a funkcja checkValue zostanie poprawnie wywołana i zwróci rezultat.

Jeśli chcemy zapobiec takiej sytuacji, możemy jawnie zadeklarować wybrane, przeładowane wersje funkcji checkValue jako usunięte:

bool checkValue(bool a_Bool) = delete;
bool checkValue(char a_Char) = delete;

Jeśli kompilator zauważy wersję bardziej pasującą do typu podanego argumentu, wybierze ją i od razu stwierdzi, że takie wywołanie jest zabronione. Wyklucza to niejawną konwersję argumentu w celu dopasowania do istniejącej deklaracji funkcji, przyjmującej liczbę całkowitą.

Funckje usunięte jako przemyślane udoskonalenie w nowej wersji języka.

Funkcje usunięte są kolejnym przykładem na to, jak nieskompikowana zmiana wpływa na znaczące uproszczenie języka C++ i uodpornienie go na błędy. Dzięki takim pozornie niewielkim „smaczkom” można szybko docenić standard C++11 i zrezygnować ze stosowania uciążliwych „haków” w naszym kodzie.

Kontynuuj poznawanie C++11: C++11 #10: Inteligentne wskaźniki wchodzą do gry: std::unique_ptr.

Dodaj komentarz