Verhindern Sie, dass die Funktion const std :: string & 0 akzeptiert

97

Mehr als tausend Worte:

#include<string>
#include<iostream>

class SayWhat {
    public:
    SayWhat& operator[](const std::string& s) {
        std::cout<<"here\n"; // To make sure we fail on function entry
        std::cout<<s<<"\n";
        return *this;
    }
};

int main() {
    SayWhat ohNo;
    // ohNo[1]; // Does not compile. Logic prevails.
    ohNo[0]; // you didn't! this compiles.
    return 0;
}

Der Compiler beschwert sich nicht, wenn er die Nummer 0 an den Bracket-Operator übergibt, der eine Zeichenfolge akzeptiert. Stattdessen wird dies kompiliert und schlägt vor dem Aufrufen der Methode fehl mit:

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct null not valid

Als Referenz:

> g++ -std=c++17 -O3 -Wall -Werror -pedantic test.cpp -o test && ./test
> g++ --version
gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)

Meine Vermutung

Der Compiler verwendet implizit den std::string(0)Konstruktor, um die Methode einzugeben, was das gleiche Problem (google den obigen Fehler) ohne guten Grund ergibt.

Frage

Gibt es überhaupt eine Möglichkeit, dies auf der Klassenseite zu beheben, sodass der API-Benutzer dies nicht spürt und der Fehler beim Kompilieren erkannt wird?

Das heißt, eine Überlastung hinzufügen

void operator[](size_t t) {
    throw std::runtime_error("don't");
}

ist keine gute Lösung.

Kabanus
quelle
2
Kompilierter Code, der eine Ausnahme in Visual Studio bei ohNo [0] mit der Ausnahme "0xC0000005: Zugriffsverletzungs-Leseort 0x00000000"
auslöst
5
Deklarieren Sie eine private Überladung operator[](), die ein intArgument akzeptiert , und definieren Sie es nicht.
Peter
2
@ Peter Obwohl Notiz tun, es ist ein Linker Fehler, der als noch besser ist es, was ich hatte.
Kabanus
5
@kabanus Im obigen Szenario handelt es sich um einen Compilerfehler , da der Operator privat ist! Linker-Fehler nur, wenn innerhalb der Klasse aufgerufen ...
Aconcagua
5
@ Peter Das ist vor allem in Szenarien interessant , wo kein C ++ 11 verfügbar ist - und diese tun es heute noch (eigentlich bin ich in einem Projekt mit beschäftigen, und ich bin ziemlich einige der neuen Funktionen fehlen ... ).
Aconcagua

Antworten:

161

Der Grund std::string(0)ist gültig, weil 0er eine Nullzeigerkonstante ist. 0 entspricht also dem String-Konstruktor, der einen Zeiger nimmt. Dann läuft der Code gegen die Voraussetzung, dass man keinen Nullzeiger übergeben darf std::string.

Nur das Literal 0würde als Nullzeigerkonstante interpretiert, wenn es ein Laufzeitwert in einem intwäre, hätten Sie dieses Problem nicht (da dann die Überlastungsauflösung stattdessen nach einer intKonvertierung suchen würde ). Literal ist auch 1kein Problem, da 1es sich nicht um eine Nullzeigerkonstante handelt.

Da es sich um ein Problem mit der Kompilierungszeit handelt (wörtlich ungültige Werte), können Sie es zur Kompilierungszeit abfangen. Fügen Sie eine Überladung dieses Formulars hinzu:

void operator[](std::nullptr_t) = delete;

std::nullptr_tist die Art von nullptr. Und es wird passen jeden Null - Zeiger - Konstante, wäre es 0, 0ULLoder nullptr. Und da die Funktion gelöscht wird, verursacht sie während der Überlastungsauflösung einen Fehler bei der Kompilierung.

Geschichtenerzähler - Unslander Monica
quelle
Dies ist bei weitem die beste Lösung, völlig vergessen, dass ich einen NULL-Zeiger überladen kann.
Kabanus
In Visual Studio löst sogar "ohNo [0]" eine Nullwertausnahme aus. Bedeutet dies eine Implementierung, die für die Klasse std :: string spezifisch ist?
TruthSeeker
@pmp Was ausgelöst wird (wenn überhaupt), ist implementierungsspezifisch, aber der Punkt ist, dass die Zeichenfolge in allen ein NULL-Zeiger ist. Mit dieser Lösung gelangen Sie nicht zum Ausnahmeteil, sondern wird beim Kompilieren erkannt.
Kabanus
18
@pmp - Die Übergabe eines Nullzeigers an std::stringden Konstruktor ist nach dem C ++ - Standard nicht zulässig. Es ist ein undefiniertes Verhalten, sodass MSVC tun kann, was es will (z. B. eine Ausnahme auslösen).
Geschichtenerzähler - Unslander Monica
26

Eine Möglichkeit besteht darin, eine privateÜberladung zu deklarieren operator[](), die ein integrales Argument akzeptiert, und es nicht zu definieren.

Diese Option funktioniert mit allen C ++ - Standards (ab 1998), im Gegensatz zu Optionen, void operator[](std::nullptr_t) = deletedie ab C ++ 11 gültig sind.

Wenn Sie operator[]()ein privateMitglied erstellen, wird in Ihrem Beispiel ein diagnostizierbarer Fehler verursacht ohNo[0], es sei denn, dieser Ausdruck wird von einer Mitgliedsfunktion oder friendder Klasse verwendet.

Wenn dieser Ausdruck von einer Mitgliedsfunktion oder friendder Klasse verwendet wird, wird der Code kompiliert, aber - da die Funktion nicht definiert ist - schlägt der Build im Allgemeinen fehl (z. B. ein Linkerfehler aufgrund einer undefinierten Funktion).

Peter
quelle