Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W języku C++11 funkcje domyślne i usunięte zapewniają jawną kontrolę nad tym, czy specjalne funkcje członkowskie są generowane automatycznie. Usunięte funkcje zapewniają również prosty język, aby zapobiec występowaniu problematycznych promocji typów w argumentach do funkcji wszystkich typów — specjalnych funkcji składowych oraz normalnych funkcji składowych i funkcji innych niż liczba — co w przeciwnym razie spowodowałoby niepożądane wywołanie funkcji.
Zalety jawnie domyślnych i usuniętych funkcji
W języku C++kompilator automatycznie generuje konstruktor domyślny, konstruktor kopiowania, operator przypisania kopiowania i destruktor dla typu, jeśli nie deklaruje własnego. Te funkcje są nazywane specjalnymi funkcjami składowymi i sprawiają, że proste typy zdefiniowane przez użytkownika w języku C++ zachowują się jak struktury w języku C. Oznacza to, że można tworzyć, kopiować i niszczyć je bez dodatkowego nakładu pracy programistycznego. W standardzie C++11 dodano do języka przenoszenie semantyki oraz konstruktor przenoszący i operator przypisania przeniesienia do listy specjalnych funkcji członkowskich, które kompilator może wygenerować automatycznie.
Jest to wygodne w przypadku prostych typów, ale złożone typy często definiują jedną lub więcej specjalnych funkcji składowych, co może uniemożliwić automatyczne generowanie innych specjalnych funkcji składowych. 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:
Konstruktor kopiowania nie jest generowany automatycznie.
Żaden operator przypisania 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 przenoszenia nie jest generowany automatycznie.
Żaden operator przypisania 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 kopiującego jest uznawane za przestarzałe.
W obu przypadkach program Visual Studio automatycznie generuje niezbędne funkcje niejawnie i domyślnie nie emituje ostrzeżenia. Ponieważ program Visual Studio 2022 w wersji 17.7, można włączyć C5267 w celu emitowania ostrzeżenia.
Konsekwencje tych reguł mogą również wyciec do hierarchii obiektów. Jeśli na przykład z jakiegokolwiek powodu klasa bazowa nie może mieć domyślnego konstruktora, który można wywołać z klasy pochodnej , czyli konstruktora public
protected
, który nie przyjmuje żadnych parametrów, wówczas klasa, która pochodzi z niej, nie może automatycznie wygenerować własnego konstruktora domyślnego.
Te reguły mogą komplikować implementację tego, co powinno być proste, zdefiniowane przez użytkownika typy i typowe idiomy języka C++ — na przykład tworzenie typu zdefiniowanego przez użytkownika niezwiązanego z kopią przez deklarowanie konstruktora kopii i operatora przypisania kopiowania prywatnie i nie definiując ich.
struct noncopyable
{
noncopyable() {};
private:
noncopyable(const noncopyable&);
noncopyable& operator=(const noncopyable&);
};
Przed C++11 ten fragment kodu był idiotyczną formą typów niezwiązanych z kopiowaniem. Jednak ma kilka problemów:
Konstruktor kopii musi być zadeklarowany prywatnie, aby go ukryć, ale ponieważ jest zadeklarowany w ogóle, automatyczne generowanie konstruktora domyślnego jest blokowane. Jeśli chcesz, musisz jawnie zdefiniować konstruktor domyślny, nawet jeśli nic nie zrobi.
Nawet jeśli jawnie zdefiniowany konstruktor domyślny nic nie robi, kompilator uzna go za nietrivial. 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 przed kodem zewnętrznym, to funkcje członkowskie i elementy zaprzyjaźnione
noncopyable
nadal mogą je widzieć i wywoływać. Jeśli są zadeklarowane, ale nie zdefiniowano, wywołanie ich powoduje błąd konsolidatora.Chociaż jest to powszechnie akceptowany idiom, intencja nie jest jasna, chyba że rozumiesz wszystkie reguły automatycznego generowania specjalnych funkcji członkowskich.
W języku C++11 idiom nieznośny można zaimplementować w sposób prostszy.
struct noncopyable
{
noncopyable() =default;
noncopyable(const noncopyable&) =delete;
noncopyable& operator=(const noncopyable&) =delete;
};
Zwróć uwagę, że problemy z idiomem pre-C++11 zostały rozwiązane:
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.
Jawnie domyślne specjalne funkcje składowe są nadal uważane za proste, więc nie ma kary za wydajność i
noncopyable
nie jest blokowany jako prawdziwy typ zasobnika.Konstruktor kopiowania i operator przypisania kopiowania są publiczne, ale usuwane. Jest to błąd czasu kompilacji do zdefiniowania lub wywołania usuniętej funkcji.
Intencja jest jasna dla każdego, kto rozumie
=default
i=delete
. Nie musisz rozumieć reguł automatycznego generowania specjalnych funkcji składowych.
Podobne idiomy istnieją do tworzenia typów zdefiniowanych przez użytkownika, które nie są wymienne, które mogą być przydzielane dynamicznie lub których nie można przydzielać dynamicznie. Każdy z tych idiomów ma implementacje przed C++11, które cierpią z podobnymi problemami, i które są podobnie rozwiązane w języku C++11, implementując je pod względem domyślnych i usuniętych specjalnych funkcji składowych.
Jawnie domyślne funkcje
Domyślnie można ustawić dowolną specjalną funkcję składową — aby jawnie stwierdzić, że specjalna funkcja składowa używa domyślnej implementacji, aby zdefiniować specjalną funkcję składową z kwalifikatorem dostępu niepublikowanego lub przywrócić specjalną funkcję składową, której automatyczne generowanie zostało uniemożliwione przez inne okoliczności.
Domyślną funkcją składową jest funkcja specjalna, deklarując ją tak jak w tym przykładzie:
struct widget
{
widget()=default;
inline widget& operator=(const widget&);
};
inline widget& widget::operator=(const widget&) =default;
Zwróć uwagę, że można domyślnie ustawić specjalną funkcję składową poza treścią klasy, o ile jest to możliwe.
Ze względu na zalety wydajności funkcji specjalnych elementów członkowskich zalecamy, aby wolisz automatycznie generowane specjalne funkcje składowe nad pustymi ciałami funkcji, gdy chcesz użyć domyślnego zachowania. Można to zrobić za pomocą jawnego ustawienia domyślnego specjalnej funkcji składowej lub nie deklarując jej (a także nie deklarując innych specjalnych funkcji członkowskich, które uniemożliwiłyby jej automatyczne generowanie).
Usunięte funkcje
Można usunąć specjalne funkcje składowe i normalne funkcje składowe i funkcje nieczłonkowe, aby zapobiec ich zdefiniowaniu lub wywołaniu. Usunięcie specjalnych funkcji składowych zapewnia bardziej przejrzysty sposób zapobiegania generowaniu przez kompilator specjalnych funkcji składowych, których nie chcesz. Funkcja musi zostać usunięta, ponieważ jest zadeklarowana; Nie można go później usunąć w sposób, w jaki można zadeklarować funkcję, a następnie później domyślną.
struct widget
{
// deleted operator new prevents widget from being dynamically allocated.
void* operator new(std::size_t) = delete;
};
Usunięcie normalnych funkcji składowych lub funkcji innych niż liczba uniemożliwia wywoływanie niezamierzonej funkcji typu. Działa to, ponieważ usunięte funkcje nadal uczestniczą w rozwiązywaniu przeciążeń i zapewniają lepsze dopasowanie niż funkcja, którą można wywołać po podwyższeniu poziomu 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; }
Zwróć uwagę, że w poprzednim przykładzie wywołanie przy użyciu float
argumentu spowodowałoby błąd kompilatora, ale wywołanie call_with_true_double_only
przy użyciu int
argumentu nie byłoby. W takim int
przypadku argument zostanie podwyższony z int
elementu do double
i pomyślnie wywoła double
wersję funkcji, mimo że może to nie być intencja.call_with_true_double_only
Aby upewnić się, że każde wywołanie tej funkcji przy użyciu argumentu innego niż podwójne powoduje błąd kompilatora, można zadeklarować wersję szablonu usuniętej 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.