Znaczenie = usuń po deklaracji funkcji

242
class my_class
{
    ...
    my_class(my_class const &) = delete;
    ...
};

Co = deleteznaczy w tym kontekście?

Czy są jakieś inne „modyfikatory” (inne niż = 0i = delete)?

Pat O'Keefe
źródło
23
@Blindy Będzie to standard w C ++ 0x, czyli wkrótce.
Konrad Rudolph
1
Poprawiłem się, brakowało mi tej funkcji C ++ 0x. Myślałem, że to #definela a Qt, który został oceniony na 0, a następnie zadeklarował ukrytą funkcję lub coś w tym rodzaju.
Blindy,
Przypomniałem sobie słowo kluczowe „wyłącz”, co oznacza to samo lub coś podobnego. Czy to sobie wyobrażam? Czy jest między nimi subtelna różnica?
Stewart

Odpowiedzi:

201

Usunięcie funkcji jest funkcją C ++ 11 :

Wspólny idiom „zakazu kopiowania” można teraz wyrazić bezpośrednio:

class X {
    // ...
    X& operator=(const X&) = delete;  // Disallow copying
    X(const X&) = delete;
};

[...]

Mechanizm „usuń” można zastosować do dowolnej funkcji. Na przykład możemy wyeliminować niepożądaną konwersję, taką jak ta:

struct Z {
    // ...

    Z(long long);     // can initialize with an long long         
    Z(long) = delete; // but not anything less
};
Prasoon Saurav
źródło
3
Czy tradycyjna metoda „nie zezwala na kopiowanie” tylko po to, aby copy-ctor and operator = „private”? To idzie nieco dalej i instruuje kompilator, aby nawet nie generował funkcji. Jeśli są one zarówno prywatne, jak i = delete, czy kopiowanie jest podwójnie zabronione?
Reb. Kab.
8
@Reb, =deletesprawia , że metoda jest niedostępna nawet z kontekstów, które mogą zobaczyć privatemetody (tj. W klasie i jej przyjaciołach). To usuwa wszelką niepewność podczas czytania kodu. @Prasoon, ten drugi przykład wciąż usuwa tylko konstruktory - fajnie byłoby zobaczyć operator long ()na przykład usunięte .
Toby Speight
2
@ Reb.Cabin Korzystanie = deletejest lepsze niż używanie privatelub inne podobne mechanizmy, ponieważ zazwyczaj chcesz, aby zabroniona funkcja była widocznie zadeklarowana i uwzględniona w celu rozwiązania problemu przeciążenia itp., Aby mogła zakończyć się jak najwcześniej i dać użytkownikowi wyraźny błąd. Każde rozwiązanie obejmujące „ukrywanie” deklaracji zmniejsza ten efekt.
Leushenko
1
Czy istnieje jakiś szczególny powód, aby upublicznić konstruktor i zastosować słowo kluczowe delete. Dlaczego nie pozostawić konstruktora prywatnemu i zastosować słowo kluczowe?
Dohn Joe
81
  1. = 0oznacza, że ​​funkcja jest czysto wirtualna i nie można utworzyć instancji obiektu z tej klasy. Musisz z niego skorzystać i wdrożyć tę metodę
  2. = deleteoznacza, że ​​kompilator nie wygeneruje tych konstruktorów. AFAIK jest to dozwolone tylko w przypadku konstruktora kopiowania i operatora przypisania. Ale nie jestem zbyt dobry w nadchodzącym standardzie.
mkaes
źródło
4
Istnieje kilka innych zastosowań =deleteskładni. Możesz na przykład użyć go, aby jawnie zabronić jakiejś niejawnej konwersji, która może mieć miejsce podczas połączenia. W tym celu wystarczy usunąć przeciążone funkcje. Zajrzyj na stronę Wikipedii w C ++ 0x, aby uzyskać więcej informacji.
LiKao
Zrobię to, gdy tylko coś znajdę. Chyba nadszedł czas, aby dogonić c ++ 0X
mkaes
Tak, C ++ 0x skały. Nie mogę się doczekać, aż GCC 4.5+ będzie bardziej powszechny, więc mogę zacząć używać lambdas.
LiKao
5
Opis = deletenie jest całkowicie poprawny. = deletemoże być użyty do dowolnej funkcji, w którym to przypadku jest wyraźnie oznaczony jako usunięty, a każde użycie powoduje błąd kompilatora. W przypadku specjalnych funkcji składowych oznacza to w szczególności, że nie są one generowane przez kompilator, ale jest to tylko wynikiem usunięcia, a nie tego, co = deletenaprawdę jest.
MicroVirus,
28

Ten fragment książki The C ++ Programming Language [4th Edition] - Bjarne Stroustrup mówi o prawdziwym celu wykorzystania =delete:

3.3.4 Tłumienie operacji

Użycie domyślnej kopii lub przeniesienia dla klasy w hierarchii jest zwykle katastrofą : biorąc pod uwagę tylko wskaźnik do bazy, po prostu nie wiemy, którzy członkowie mają klasę pochodną, ​​więc nie możemy wiedzieć, jak je skopiować . Dlatego najlepiej jest usunąć domyślne operacje kopiowania i przenoszenia, czyli wyeliminować domyślne definicje tych dwóch operacji:

class Shape {
public:
  Shape(const Shape&) =delete; // no copy operations
  Shape& operator=(const Shape&) =delete;

  Shape(Shape&&) =delete; // no move operations
  Shape& operator=(Shape&&) =delete;
  ˜Shape();
    // ...
};

Teraz kompilator przechwytuje próbę skopiowania kształtu.

=deleteMechanizm jest ogólnie, to znaczy, może on być stosowany w celu powstrzymania każdą operację

Saurav Sahu
źródło
5

Standardy kodowania, z którymi pracowałem, były następujące dla większości deklaracji klasowych.

//  coding standard: disallow when not used
T(void)                  = delete; // default ctor    (1)
~T(void)                 = delete; // default dtor    (2)
T(const T&)              = delete; // copy ctor       (3)
T(const T&&)             = delete; // move ctor       (4)
T& operator= (const T&)  = delete; // copy assignment (5)
T& operator= (const T&&) = delete; // move assignment (6)

Jeśli użyjesz któregoś z tych 6, po prostu skomentuj odpowiedni wiersz.

Przykład: klasa FizzBus wymaga tylko dtor, a zatem nie używa pozostałych 5.

//  coding standard: disallow when not used
FizzBuzz(void)                         = delete; // default ctor (1)
// ~FizzBuzz(void);                              // dtor         (2)
FizzBuzz(const FizzBuzz&)              = delete; // copy ctor    (3)
FizzBuzz& operator= (const FizzBuzz&)  = delete; // copy assig   (4)
FizzBuzz(const FizzBuzz&&)             = delete; // move ctor    (5)
FizzBuzz& operator= (const FizzBuzz&&) = delete; // move assign  (6)

Komentujemy tutaj tylko 1 i instalujemy implementację w innym miejscu (prawdopodobnie tam, gdzie sugeruje to standard kodowania). Pozostałe 5 (z 6) jest niedozwolone przy usuwaniu.

Możesz także użyć „= usuń”, aby zabronić niejawnych promocji wartości o różnych rozmiarach ... przykład

// disallow implicit promotions 
template <class T> operator T(void)              = delete;
template <class T> Vuint64& operator=  (const T) = delete;
template <class T> Vuint64& operator|= (const T) = delete;
template <class T> Vuint64& operator&= (const T) = delete;
2785528
źródło
3

= deleteto funkcja wprowadzona w C ++ 11. Zgodnie z =deletetym nie będzie można wywoływać tej funkcji.

Szczegółowo.

Załóżmy w klasie.

Class ABC{
 Int d;
 Public:
  ABC& operator= (const ABC& obj) =delete
  {

  }
};

Wywoływanie tej funkcji w celu przypisania obiektów nie jest dozwolone. Oznacza, że ​​operator przypisania ograniczy kopiowanie z jednego obiektu do drugiego.

Ashutosh
źródło
2

Nowy standard C ++ 0x. Patrz sekcja 8.4.3 w projekcie roboczym N3242

dubnde
źródło
Whoa, ten projekt jest już nieaktualny. Oto najnowszy (z 3 kwietnia 2011 r.): Open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf
TonyK
Dzięki i zaktualizowałem link. Bardzo pomocne, aby uzyskać bieżącą wersję roboczą. Odnośna sekcja / treść była poprawna nawet w starym projekcie, więc nie rozumiem głosowania negatywnego.
dubnde,
1

Usunięta funkcja jest domyślnie wbudowana

(Dodatek do istniejących odpowiedzi)

... A usunięta funkcja będzie pierwszą deklaracją funkcji (z wyjątkiem usuwania wyraźnych specjalizacji szablonów funkcji - usunięcie powinno nastąpić przy pierwszej deklaracji specjalizacji), co oznacza, że ​​nie można zadeklarować funkcji, a następnie usunąć, powiedzmy, w swojej definicji lokalnej dla jednostki tłumaczeniowej.

Powołując się na [dcl.fct.def.delete] / 4 :

Usunięta funkcja jest domyślnie wbudowana. ( Uwaga: Reguła z jedną definicją ( [basic.def.odr] ) ma zastosowanie do usuniętych definicji. - uwaga końcowa] Usunięta definicja funkcji powinna być pierwszą deklaracją funkcji lub, w przypadku wyraźnej specjalizacji szablonu funkcji , pierwsza deklaracja tej specjalizacji. [Przykład:

struct sometype {
  sometype();
};
sometype::sometype() = delete;      // ill-formed; not first declaration

- koniec przykładu )

Szablon funkcji podstawowej z usuniętą definicją może być wyspecjalizowany

Chociaż ogólną zasadą jest unikanie specjalizacji szablonów funkcyjnych, ponieważ specjalizacje nie biorą udziału w pierwszym etapie rozwiązywania problemu przeciążenia, istnieją pewne konteksty, w których może to być przydatne. Np. Przy użyciu nie przeciążonego szablonu funkcji podstawowej bez definicji, aby dopasować wszystkie typy, których nie chciałoby się pośrednio konwertować na przeciążenie polegające na dopasowaniu przez konwersję; tj. w celu niejawnego usunięcia szeregu niejawnych dopasowań konwersji poprzez implementację tylko dopasowań dokładnych typów w wyraźnej specjalizacji niezdefiniowanego, nie przeciążonego szablonu funkcji podstawowej.

Przed koncepcją usuniętej funkcji w C ++ 11 można to zrobić, po prostu pomijając definicję szablonu funkcji podstawowej, ale dawało to niejasne, nieokreślone błędy referencyjne, które prawdopodobnie nie dawały żadnej intencji semantycznej autorowi szablonu funkcji podstawowej (celowo pominięto ?). Jeśli zamiast tego jawnie usuniemy szablon funkcji podstawowej, komunikaty o błędach w przypadku, gdy nie zostanie znaleziona odpowiednia wyraźna specjalizacja, staną się znacznie przyjemniejsze, a także pokażą, że pominięcie / usunięcie definicji szablonu funkcji podstawowej było celowe.

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t);

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    //use_only_explicit_specializations(str); // undefined reference to `void use_only_explicit_specializations< ...
}

Jednak zamiast po prostu pominąć definicję powyższego szablonu funkcji podstawowej, powodując niejasny, niezdefiniowany błąd odniesienia, gdy żadna wyraźna specjalizacja nie pasuje, podstawową definicję szablonu można usunąć:

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t) = delete;

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    use_only_explicit_specializations(str);
    /* error: call to deleted function 'use_only_explicit_specializations' 
       note: candidate function [with T = std::__1::basic_string<char>] has 
       been explicitly deleted
       void use_only_explicit_specializations(T t) = delete; */
}

Daje bardziej czytelny komunikat o błędzie, w którym zamiar usunięcia jest również wyraźnie widoczny (gdzie nieokreślony błąd odniesienia może prowadzić do tego, że programista uzna to za nierozważny błąd).

Wracając do tego, dlaczego mielibyśmy chcieć korzystać z tej techniki? Ponownie, wyraźne specjalizacje mogą być przydatne do niejawnego usuwania niejawnych konwersji.

#include <cstdint>
#include <iostream>

void warning_at_best(int8_t num) { 
    std::cout << "I better use -Werror and -pedantic... " << +num << "\n";
}

template< typename T >
void only_for_signed(T t) = delete;

template<>
void only_for_signed<int8_t>(int8_t t) {
    std::cout << "UB safe! 1 byte, " << +t << "\n";
}

template<>
void only_for_signed<int16_t>(int16_t t) {
    std::cout << "UB safe! 2 bytes, " << +t << "\n";
}

int main()
{
    const int8_t a = 42;
    const uint8_t b = 255U;
    const int16_t c = 255;
    const float d = 200.F;

    warning_at_best(a); // 42
    warning_at_best(b); // implementation-defined behaviour, no diagnostic required
    warning_at_best(c); // narrowing, -Wconstant-conversion warning
    warning_at_best(d); // undefined behaviour!

    only_for_signed(a);
    only_for_signed(c);

    //only_for_signed(b);  
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = unsigned char] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */

    //only_for_signed(d);
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = float] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */
}
dfri
źródło
0

Jest to nowa rzecz w standardach C ++ 0x, w której można usunąć odziedziczoną funkcję.

Tayyab
źródło
11
Możesz usunąć dowolną funkcję. Np. void foo(int); template <class T> void foo(T) = delete;Zatrzymuje wszystkie niejawne konwersje. intAkceptowane są tylko argumenty typu, wszystkie inne spróbują utworzyć instancję funkcji „usuniętej”.
UncleBens,