Jawnie domyślne i usunięte funkcje
W języku C++ 11 funkcje domyślne i usunięte dają jawną kontrolę nad tym, czy specjalne funkcje członkowskie są generowane automatycznie.Funkcje usunięte dają również prosty język do zapobiegania występowaniu problematycznych promocji typu w argumentach funkcji wszystkich typów – zarówno w funkcjach specjalnych elementów członkowskich, jak i w funkcjach normalnych elementów członkowskich oraz funkcjach nie będących elementami członkowskimi – które mogą w przeciwnym razie spowodować niechciane wywołania funkcji.
Korzyści z jawnie ustawianych jako domyślne i usuniętych funkcji
W języku C++, kompilator automatycznie generuje dla typu konstruktor domyślny, konstruktor kopiujący, operator przypisania kopii oraz destruktor, jeśli typ nie deklaruje swoich własnych.Funkcje te są znane jako specjalne funkcje członkowskie, i są one tym, co sprawia, że zdefiniowane przez użytkownika typy proste w języku C++ zachowują się jak struktury w C.Oznacza to, że można je tworzyć, kopiować i niszczyć bez dodatkowych wysiłków związanych z kodowaniem.W standardzie C++11 dodano do języka przenoszenie semantyki oraz konstruktor przenoszący i operator przypisania przeniesienia do listy funkcji specjalnych elementów członkowskich, które kompilator może wygenerować automatycznie.
Jest to wygodne dla typów prostych, ale typy złożone często definiują jedną lub więcej specjalnych funkcji członkowskich, a to może uniemożliwiać automatyczne generowanie innych specjalnych funkcji członkowskich.W praktyce:
Jeśli dowolny konstruktor jest zadeklarowany w sposób jawny, to żaden domyślny konstruktor nie jest generowany automatycznie.
Jeśli destruktor wirtualny jest zadeklarowany w sposób jawny, to żaden domyślny destruktor nie jest generowany automatycznie.
Jeśli konstruktor przenoszący lub operator przypisania przeniesienia jest zadeklarowany w sposób jawny, to:
Żaden konstruktor kopiujący nie jest generowany automatycznie.
Żaden operator przypisywania kopiowania nie jest generowany automatycznie.
Jeśli konstruktor kopiujący, operator przypisania kopiowania, konstruktor przeniesienia, operator przypisania przeniesienia lub destruktor jest zadeklarowany w sposób jawny, to:
Żaden konstruktor przenoszący nie jest generowany automatycznie.
Żaden operator przypisywania przenoszenia nie jest generowany automatycznie.
[!UWAGA]
Ponadto, standard C++11 określa następujące reguły dodatkowe:
Jeśli konstruktor kopiujący lub destruktor jest zadeklarowany w sposób jawny, to automatyczne generowanie operatora przypisania kopiowania jest uznawane za przestarzałe.
Jeśli operator przypisania kopiowania lub destruktor jest zadeklarowany w sposób jawny, to automatyczne generowanie konstruktora kopiowania jest uznawane za przestarzałe.
W obu przypadkach, program Visual Studio kontynuuje automatyczne generowanie niezbędnych funkcji niejawnie i nie emituje ostrzeżenia.
Konsekwencje tych reguł mogą również wyciec do hierarchii obiektów.Na przykład, jeżeli z jakiegokolwiek powodu klasa podstawowa nie będzie mieć domyślnego konstruktora wywoływanego z klasy pochodnej (to znaczy konstruktora o widoczności public lub protected, który nie przyjmuje żadnych parametrów), to klasa pochodna nie będzie mogła automatycznie wygenerować swojego własnego konstruktora domyślnego.
Te reguły mogą skomplikować implementowanie tego, co powinno być prostymi, zdefiniowanymi przez użytkownika typami i wspólnymi idiomami języka C++ — na przykład sprawianie, że typ zdefiniowany przez użytkownika nie będzie mógł być kopiowany poprzez prywatne deklarowanie konstruktor kopiowania i operatora przypisania kopii i nie definiowanie ich.
struct noncopyable
{
noncopyable() {};
private:
noncopyable(const noncopyable&);
noncopyable& operator=(const noncopyable&);
};
Przed C++11 ten fragment kodu był formą idiomatyczną typów niekopiowalnych.Jednak ma kilka problemów:
Konstruktor kopiowania musi być zadeklarowany prywatnie, aby go ukryć, ale ponieważ jest on zadeklarowany w ogóle, automatyczne generowanie konstruktora domyślnego zostało powstrzymane.Musisz jawnie zdefiniować konstruktor domyślny, jeżeli chcesz go posiadać, nawet jeżeli nic nie robi.
Nawet jeśli jawnie zdefiniowany konstruktor domyślny nic nie robi, kompilator uznaje to za nietrywialne.Jest on mniej skuteczny niż automatycznie generowany konstruktor domyślny i uniemożliwia elementom noncopyable bycie prawdziwym typem POD.
Nawet jeśli konstruktor kopiujący i operator przypisania kopiowania są ukryte na zewnątrz kodu, to funkcje elementów członkowskich i przyjazne elementy noncopyable nadal mogą je widzieć i wywoływać.Jeśli są one zadeklarowane, ale nie zdefiniowane, wywołanie ich powoduje błąd konsolidatora.
Chociaż jest to powszechnie akceptowany idiom, zamiar nie jest jasny, chyba że rozumiesz wszystkie reguły automatycznego generowania funkcji specjalnych elementów członkowskich.
W języku C++11 idiom "non-copyable" może być implementowany w sposób, który jest bardziej bezpośredni.
struct noncopyable
{
noncopyable() =default;
noncopyable(const noncopyable&) =delete;
noncopyable& operator=(const noncopyable&) =delete;
};
Zauważ, jak są rozwiązywane problemy z idiomem w języku przed C++ 11:
Generowanie konstruktora domyślnego nadal jest uniemożliwiane przez zadeklarowanie konstruktora kopiującego, ale można go przywrócić przez jawne ustawienie go jako domyślnego.
Funkcje specjalnych elementów członkowskich jawnie ustawione jako domyślne są nadal uważane za trywialne, zatem nie zachodzi pogorszenie wydajności, a elementy noncopyable nie są powstrzymywane przed staniem się prawdziwymi typami POD.
Konstruktor kopiowania i operator przydzielania kopii są publiczne, ale zostały usunięte.Jest to błąd czasu kompilacji, aby definiować lub wywoływać funkcję usuniętą.
Intencja jest jasna dla każdego, kto rozumie =default i =delete.Nie musisz rozumieć zasad generacji automatycznej funkcji specjalnych funkcji członkowskich.
Istnieją podobne idiomy do tworzenia typów zdefiniowanych przez użytkownika, które są nieruchome, które można jedynie dynamicznie przydzielać lub których nie można przydzielać dynamicznie.Każdy z tych idiomów ma implementacje pre-C++11, w których występują podobne problemy, i są podobnie rozwiązane w C++11 — przez ich implementację zgodnie z domyślnymi i usuniętymi specjalnymi funkcjami elementów członkowskich.
Funkcje jawnie przyjmujące wartości domyślne
Można ustawić jako domyślne dowolne specjalne funkcje członkowskie — aby stwierdzić, że specjalna funkcja członkowska używa implementacji domyślnej, określić specjalną funkcję członkowską z kwalifikatorem dostęp niepubliczny, lub przywrócić specjalną funkcję członkowską, której automatyczna generacja została uniemożliwiona przez inne okoliczności.
Ustawiasz jako domyślną specjalną funkcję członkowską deklarując ją jak w poniższym przykładzie:
struct widget
{
widget()=default;
inline widget& operator=(const widget&);
};
inline widget& widget::operator=(const widget&) =default;
Zwróć uwagę, że można ustawić domyślną specjalną funkcję członkowską poza treścią klasy tak długo, jak długo można ją wbudować.
Ze względu na korzyści związane z wydajnością zaawansowanych funkcji specjalnych elementów członkowskich zalecamy automatyczne generowanie specjalnych elementów członkowskich przez pustą funkcję, jeśli użytkownik wybierze zachowanie domyślne.Można to zrobić ustawiając jako domyślną specjalną funkcję członkowską, albo nie deklarując jej (i również nie deklarując innych specjalnych funkcji członkowskich, które uniemożliwiałyby jej automatyczne generowanie).
[!UWAGA]
Program Visual Studio nie obsługuje domyślnych konstruktorów przenoszących ani operatorów przypisania przeniesienia, których wymaga standard C++11.Aby uzyskać więcej informacji, zobacz sekcję Domyślne i usunięte funkcje w temacie Obsługa dla funkcji C++11 (Modern C++).
Funkcje usunięte.
Można usunąć specjalne funkcje członkowskie, a także normalne funkcje członkowskie i funkcje nieczłonkowskie, aby zapobiec definiowaniu lub nazywaniu ich.Usuwanie specjalnych funkcji członkowskich zapewnia bardziej przejrzysty sposób zapobiegania generowaniu przez kompilatora niechcianych specjalnych funkcji członkowskich.Funkcja muszą zostać usunięte, ponieważ są one przestarzałe; z tego powodu nie mogą zostać usunięte w sposób, w jaki funkcja może zostać zadeklarowana, a następnie ustawiona jako domyślna.
struct widget
{
// deleted operator new prevents widget from being dynamically allocated.
void* operator new(std::size_t) =delete;
};
Usuwanie normalnej funkcji członkowskiej lub nieczłonkowskiej zapobiega problematycznym promocjom spowodowanym przez niezamierzone wywołanie funkcji.To działa, ponieważ usunięte funkcje nadal uczestniczą w rozpoznawaniu przeciążenia i zapewniają lepsze dopasowane niż funkcja, która może być wywoływana po wypromowaniu typów.Wywołanie funkcji jest rozpoznawane jako funkcja bardziej specyficzna (jednak usunięta) i powoduje błąd kompilatora.
// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }
Zauważ, że w poprzednim przykładzie wywołanie call_with_true_double_only przy użyciu argumentu o typie float spowodowałoby błąd kompilatora, ale wywołanie call_with_true_double_only przy użyciu argumentu o typie int nie; w przypadku wartości int, argument zostanie promowany z typu int do double i pomyślnie wywoła funkcję w wersji double, nawet jeśli mogło to nie być zamierzone.Aby upewnić się, że każde wywołanie funkcji przy użyciu argumentu o typie innym niż double spowoduje błąd kompilacji, możesz zadeklarować wersję szablonową usuwanej funkcji.
template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.
void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.