Warum nehmen C ++ 11-gelöschte Funktionen an der Überlastungsauflösung teil?

Antworten:

114

Die Hälfte des Zwecks der = deleteSyntax besteht darin, zu verhindern, dass Personen bestimmte Funktionen mit bestimmten Parametern aufrufen. Dies dient hauptsächlich dazu, implizite Konvertierungen in bestimmten Szenarien zu verhindern. Um eine bestimmte Überlastung zu verbieten, muss sie an der Überlastungsauflösung teilnehmen.

Die Antwort, die Sie zitieren, gibt Ihnen ein perfektes Beispiel:

struct onlydouble {
  onlydouble(std::intmax_t) = delete;
  onlydouble(double);
};

Wenn deletedie Funktion vollständig entfernt würde, würde die = deleteSyntax dieser entsprechen:

struct onlydouble2 {
  onlydouble2(double);
};

Sie könnten dies tun:

onlydouble2 val(20);

Dies ist legal C ++. Der Compiler betrachtet alle Konstruktoren. Keiner von ihnen nimmt direkt einen ganzzahligen Typ an. Aber einer von ihnen kann es nach einer impliziten Konvertierung nehmen. Also wird es das nennen.

onlydouble val(20);

Dies ist kein legales C ++. Der Compiler betrachtet alle Konstruktoren, einschließlich der deleted- Konstruktoren . Es wird eine genaue Übereinstimmung über angezeigt std::intmax_t(die genau mit jedem ganzzahligen Literal übereinstimmt). Der Compiler wählt es also aus und gibt sofort einen Fehler aus, da er eine deleted-Funktion ausgewählt hat.

= deletebedeutet "Ich verbiete das", nicht nur "Das existiert nicht". Es ist eine viel stärkere Aussage.

Ich habe gefragt, warum der C ++ - Standard sagt: = Löschen bedeutet "Ich verbiete dies" anstelle von "Dies existiert nicht".

Das liegt daran, dass wir keine spezielle Grammatik benötigen, um zu sagen, dass dies nicht existiert. Wir erhalten dies implizit, indem wir das betreffende "dies" einfach nicht deklarieren. "Ich verbiete das" stellt ein Konstrukt dar, das ohne spezielle Grammatik nicht erreicht werden kann. Wir bekommen also eine spezielle Grammatik, um zu sagen "Ich verbiete das" und nicht die andere Sache.

Die einzige Funktionalität, die Sie durch eine explizite Grammatik "Dies existiert nicht" erhalten würden, besteht darin, zu verhindern, dass jemand sie später für existent erklärt. Und das ist einfach nicht nützlich genug, um eine eigene Grammatik zu benötigen.

Andernfalls kann nicht erklärt werden, dass der Kopierkonstruktor nicht vorhanden ist, und seine Existenz kann zu unsinnigen Mehrdeutigkeiten führen.

Der Kopierkonstruktor ist eine spezielle Elementfunktion. Jede Klasse hat immer einen Kopierkonstruktor. So wie sie immer einen Kopierzuweisungsoperator, einen Verschiebungskonstruktor usw. haben.

Diese Funktionen existieren; Die Frage ist nur, ob es legal ist, sie anzurufen. Wenn Sie versuchen würden zu sagen, dass dies = deletebedeutet, dass sie nicht existieren, müsste die Spezifikation erklären, was es bedeutet, dass eine Funktion nicht existiert. Dies ist kein Konzept, das in der Spezifikation behandelt wird.

Wenn Sie versuchen, eine Funktion aufzurufen, die noch nicht deklariert / definiert wurde, tritt ein Fehler beim Compiler auf. Es tritt jedoch ein Fehler aufgrund eines undefinierten Bezeichners auf , nicht aufgrund eines Fehlers "Funktion existiert nicht" (selbst wenn Ihr Compiler dies so meldet). Verschiedene Konstruktoren werden alle durch Überlastungsauflösung aufgerufen, so dass ihre "Existenz" in dieser Hinsicht behandelt wird.

In jedem Fall gibt es entweder eine über den Bezeichner deklarierte Funktion oder einen Konstruktor / Destruktor (ebenfalls über den Bezeichner deklariert, nur einen Typbezeichner). Das Überladen von Operatoren verbirgt die Kennung hinter syntaktischem Zucker, ist aber immer noch vorhanden.

Die C ++ - Spezifikation kann das Konzept einer "nicht existierenden Funktion" nicht verarbeiten. Es kann eine Überlastungsfehlanpassung behandeln. Es kann eine Überlastungsmehrdeutigkeit behandeln. Aber es weiß nicht, was nicht da ist. So = deletewird in Bezug auf die weitaus nützlicher definiert „Versuch , dies zu nennen scheitern“ , anstatt die weniger nützlich „so tun , als ich nie diese Zeile geschrieben hat .“

Lesen Sie den ersten Teil noch einmal durch. Sie können dies nicht mit "Funktion existiert nicht" tun . Dies ist ein weiterer Grund, warum es so definiert wird: Einer der Hauptanwendungsfälle der = deleteSyntax besteht darin, den Benutzer zu zwingen, bestimmte Parametertypen zu verwenden, explizit umzuwandeln usw. Grundsätzlich implizite Typkonvertierungen vereiteln.

Ihr Vorschlag würde das nicht tun.

Nicol Bolas
quelle
1
@Mehrdad: Wenn Sie mehr Klarheit darüber benötigen, warum = Löschen nicht so funktioniert, wie Sie es möchten, müssen Sie die genaue Semantik, die Ihrer Meinung nach = Löschen haben sollte, klarer definieren . Sollte es sein "so tun, als hätte ich diese Zeile nie geschrieben?" Oder sollte es etwas anderes sein?
Nicol Bolas
1
Nein, ich meine, ich hätte erwartet = delete, dass "dieses Mitglied existiert nicht" bedeutet, was bedeuten würde, dass es nicht an der Überlastungsauflösung teilnehmen könnte.
user541686
6
@Mehrdad: Und das geht auf meinen ursprünglichen Punkt zurück, weshalb ich ihn gepostet habe: Wenn = delete"dieses Mitglied existiert nicht" gemeint ist, kann das erste Beispiel, das ich gepostet habe, nicht verhindern, dass Leute Ganzzahlen an onlydoubleden Konstruktor übergeben , da die onlydoublegelöschte Überladung nicht existieren würde . Es würde nicht an der Überlastungsauflösung teilnehmen und Sie daher nicht daran hindern, Ganzzahlen zu übergeben. Welches ist die Hälfte des Punktes der = deleteSyntax: um sagen zu können: "Sie können X nicht implizit an diese Funktion übergeben."
Nicol Bolas
3
@Mehrdad: Nach dieser Logik, warum brauchst du =deleteüberhaupt? Schließlich können wir "nicht kopierbar" sagen, indem wir genau dasselbe tun: den Kopierkonstruktor / die Zuweisung als privat deklarieren. Beachten Sie auch, dass das Deklarieren von etwas Privatem es nicht unanrufbar macht. Code innerhalb der Klasse kann ihn weiterhin aufrufen. Es ist also nicht dasselbe wie = delete. Nein, die = deleteSyntax ermöglicht es uns, etwas, das zuvor sehr unpraktisch und unergründlich war, viel offensichtlicher und vernünftiger zu machen.
Nicol Bolas
2
@Mehrdad: Weil letzteres möglich ist : Es heißt "nicht deklarieren". Dann wird es nicht existieren. Warum brauchen wir Syntax, um eine Überladung zu verbergen, anstatt private zu missbrauchen ? Fragen Sie sich wirklich, warum wir die Möglichkeit haben sollten, etwas explizit anzugeben, anstatt eine andere Funktion zu missbrauchen, um den gleichen Effekt zu erzielen ? Für jeden, der den Code liest, ist es einfach offensichtlicher, was los ist. Es macht den Code für den Benutzer leichter verständlich, erleichtert das Schreiben und behebt Probleme in der Problemumgehung. Wir haben keine brauchen lambdas entweder.
Nicol Bolas
10

Der C ++ Working Draft 2012-11-02 liefert keine Begründung für diese Regel, nur einige Beispiele

8.4.3 Gelöschte Definitionen [dcl.fct.def.delete]
...
3 [ Beispiel : Mit kann man eine nicht standardmäßige Initialisierung und eine nicht integrale Initialisierung erzwingen

struct onlydouble {  
  onlydouble() = delete; // OK, but redundant  
  onlydouble(std::intmax_t) = delete;  
  onlydouble(double);  
};  

- end example ]
[ Beispiel : Sie können die Verwendung einer Klasse in bestimmten neuen Ausdrücken verhindern, indem Sie gelöschte Definitionen eines vom Benutzer deklarierten Operators verwenden, der für diese Klasse neu ist.

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 example ]
[ Beispiel : Sie können eine Klasse nicht kopierbar machen, dh nur verschieben, indem Sie gelöschte Definitionen des Kopierkonstruktors und des Kopierzuweisungsoperators verwenden und dann Standarddefinitionen des Verschiebungskonstruktors und des Verschiebungszuweisungsoperators bereitstellen.

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  

- Beispiel beenden ]

Olaf Dietsche
quelle
4
Die Gründe scheinen aus den Beispielen sehr klar zu sein, finden Sie nicht?
Jesse Good