Dlaczego C ++ 11 powoduje, że funkcje „ delete
d” uczestniczą w rozwiązywaniu przeciążeń ?
Dlaczego jest to przydatne? Innymi słowy, dlaczego są ukryte, a nie całkowicie usunięte?
Dlaczego C ++ 11 powoduje, że funkcje „ delete
d” uczestniczą w rozwiązywaniu przeciążeń ?
Dlaczego jest to przydatne? Innymi słowy, dlaczego są ukryte, a nie całkowicie usunięte?
Odpowiedzi:
Połowa celu = delete
składni polega na tym, aby uniemożliwić ludziom wywoływanie pewnych funkcji z określonymi parametrami. Ma to głównie na celu zapobieganie niejawnym konwersjom w pewnych określonych scenariuszach. Aby zabronić określonego przeciążenia, musi uczestniczyć w rozwiązywaniu przeciążenia.
Odpowiedź, którą cytujesz, jest doskonałym przykładem:
struct onlydouble {
onlydouble(std::intmax_t) = delete;
onlydouble(double);
};
Całkowite delete
usunięcie funkcji sprawiłoby, że = delete
składnia byłaby równoważna tej:
struct onlydouble2 {
onlydouble2(double);
};
Możesz to zrobić:
onlydouble2 val(20);
To jest legalne C ++. Kompilator przyjrzy się wszystkim konstruktorom; żaden z nich nie przyjmuje bezpośrednio typu całkowitego. Ale jeden z nich może go przyjąć po niejawnej konwersji. Więc to się nazywa.
onlydouble val(20);
To nie jest legalne C ++. Kompilator przyjrzy się wszystkim konstruktorom, w tym konstruktorom delete
d. Zobaczy dokładne dopasowanie, via std::intmax_t
(które dokładnie dopasuje dowolny literał liczby całkowitej). Więc kompilator wybierze go, a następnie natychmiast wyświetli błąd, ponieważ wybrał funkcję delete
d.
= delete
znaczy „Zabraniam tego”, a nie tylko „To nie istnieje”. To znacznie mocniejsze stwierdzenie.
Pytałem, dlaczego standard C ++ mówi, że = usuń oznacza „Zabraniam tego” zamiast „to nie istnieje”
To dlatego, że nie potrzebujemy specjalnej gramatyki, aby powiedzieć „to nie istnieje”. Uzyskujemy to pośrednio, po prostu nie deklarując konkretnego „tego”, o którym mowa. „Zabraniam tego” to konstrukcja, której nie można osiągnąć bez specjalnej gramatyki. Otrzymujemy więc specjalną gramatykę, która mówi „Zabraniam tego”, a nie co innego.
Jedyną funkcjonalnością, jaką zyskałbyś, mając wyraźną gramatykę „to nie istnieje”, byłoby uniemożliwienie komuś późniejszego zadeklarowania jej istnienia. A to po prostu nie jest wystarczająco przydatne, aby potrzebować własnej gramatyki.
w przeciwnym razie nie ma sposobu, aby zadeklarować, że konstruktor kopiujący nie istnieje, a jego istnienie może powodować bezsensowne niejasności.
Konstruktor kopiujący jest specjalną funkcją składową. Każda klasa ma zawsze konstruktora kopiującego. Tak jak zawsze mają operator przypisania kopiowania, konstruktor przenoszenia itp.
Te funkcje istnieją; pytanie tylko, czy dzwonienie do nich jest legalne. Jeśli spróbujesz powiedzieć, że = delete
oznacza to, że nie istnieją, specyfikacja musiałaby wyjaśniać, co to znaczy, że funkcja nie istnieje. Nie jest to koncepcja obsługiwana przez specyfikację.
Jeśli spróbujesz wywołać funkcję, która nie została jeszcze zadeklarowana / zdefiniowana, kompilator wyświetli błąd. Ale wystąpi błąd z powodu niezdefiniowanego identyfikatora , a nie z powodu błędu „funkcja nie istnieje” (nawet jeśli Twój kompilator zgłasza to w ten sposób). Różne konstruktory są wywoływane przez rozpoznawanie przeciążenia, więc ich „istnienie” jest obsługiwane w tym kontekście.
W każdym przypadku istnieje albo funkcja zadeklarowana za pomocą identyfikatora, albo konstruktor / destruktor (również zadeklarowany za pomocą identyfikatora, tylko identyfikator typu). Przeciążenie operatora ukrywa identyfikator za cukrem składniowym, ale nadal tam jest.
Specyfikacja C ++ nie obsługuje pojęcia „funkcji, która nie istnieje”. Może obsłużyć niedopasowanie przeciążenia. Może obsłużyć niejednoznaczność przeciążenia. Ale nie wie, czego tam nie ma. Tak więc = delete
jest zdefiniowany w kategoriach o wiele bardziej użytecznych „prób nazywania tego niepowodzeniem”, a nie mniej przydatnych „udawania, że nigdy nie napisałem tej linii”.
I jeszcze raz przeczytaj ponownie pierwszą część. Nie możesz tego zrobić, gdy „funkcja nie istnieje”. To kolejny powód, dla którego jest zdefiniowany w ten sposób: ponieważ jednym z głównych przypadków użycia = delete
składni jest możliwość zmuszenia użytkownika do użycia określonych typów parametrów, jawnego rzutowania i tak dalej. Zasadniczo, aby udaremnić niejawne konwersje typów.
Twoja sugestia by tego nie zrobiła.
= delete
na myśli „ten członek nie istnieje”, co oznaczałoby, że nie mógłby uczestniczyć w rozwiązywaniu problemu przeciążenia.
= delete
oznaczało , że „ten element członkowski nie istnieje”, to pierwszy przykład, który opublikowałem, nie byłby w stanie uniemożliwić ludziom przekazywania liczb całkowitych do onlydouble
konstruktora , ponieważ onlydouble
usunięte przeciążenie nie istniałoby . Nie uczestniczyłby w rozwiązywaniu przeciążenia, a zatem nie przeszkadzałby w przekazywaniu liczb całkowitych. To jest połowa sedna = delete
składni: móc powiedzieć: „Nie możesz przekazać X niejawnie do tej funkcji”.
=delete
? W końcu możemy powiedzieć „nie do kopiowania”, robiąc dokładnie to samo: deklarując konstruktor kopiujący / przypisanie jako prywatne. Zauważ też, że zadeklarowanie czegoś prywatnego nie powoduje, że nie można tego przypisać; kod w klasie nadal może to wywołać. Więc to nie to samo co = delete
. Nie, = delete
składnia pozwala nam zrobić coś, co wcześniej było bardzo niewygodne i nieodgadnione w znacznie bardziej oczywisty i rozsądny sposób.
Robocza wersja robocza języka C ++ 2012-11-02 nie zawiera uzasadnienia dla tej reguły, tylko kilka przykładów
8.4.3 Usunięte definicje [dcl.fct.def.delete]
...
3 [ Przykład : Można wymusić inicjalizację niedomyślną i inicjalizację niecałkowitą za pomocą
struct onlydouble {
onlydouble() = delete; // OK, but redundant
onlydouble(std::intmax_t) = delete;
onlydouble(double);
};
- end przykład ]
[ Przykład : Można zapobiec użyciu klasy w pewnych nowych wyrażeniach, używając usuniętych definicji operatora zadeklarowanego przez użytkownika jako new dla tej klasy.
struct sometype {
void *operator new(std::size_t) = delete;
void *operator new[](std::size_t) = delete;
};
sometype *p = new sometype; // error, deleted class operator new
sometype *q = new sometype[3]; // error, deleted class operator new[]
- end przykład ]
[ Przykład : Można sprawić, że klasa nie będzie kopiowalna, tj. Tylko do przenoszenia, używając usuniętych definicji konstruktora kopiującego i operatora przypisania kopiującego, a następnie podając domyślne definicje konstruktora przenoszenia i operatora przypisania przenoszenia.
struct moveonly {
moveonly() = default;
moveonly(const moveonly&) = delete;
moveonly(moveonly&&) = default;
moveonly& operator=(const moveonly&) = delete;
moveonly& operator=(moveonly&&) = default;
~moveonly() = default;
};
moveonly *p;
moveonly q(*p); // error, deleted copy constructor
- przykład końca ]