Impedisci che la funzione prenda const std :: string e che accetti 0


97

Vale più di mille parole:

#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;
}

Il compilatore non si lamenta quando passa il numero 0 all'operatore parentesi quadra accettando una stringa. Invece, questo compila e fallisce prima dell'entrata nel metodo con:

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

Per riferimento:

> 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)

La mia ipotesi

Il compilatore utilizza implicitamente il file std::string(0) costruttore per inserire il metodo, che produce lo stesso problema (google l'errore sopra) senza motivo.

Domanda

Esiste un modo per risolvere questo problema dal lato della classe, quindi l'utente dell'API non lo sente e l'errore viene rilevato al momento della compilazione?

Cioè, aggiungendo un sovraccarico

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

non è una buona soluzione.


2
Codice compilato, che genera un'eccezione in Visual Studio su ohNo [0] con l'eccezione "0xC0000005: accesso alla posizione di lettura della violazione 0x00000000"
TruthSeeker,

5
Dichiarare un sovraccarico privato operator[]()che accetta un intargomento e non definirlo.
Peter,

2
@Peter Anche se nota che è un errore del linker , che è ancora meglio di quello che ho avuto.
Kabanus,

5
@kabanus Nello scenario precedente, si tratterà di un errore del compilatore , poiché l'operatore è privato! Errore linker solo se chiamato all'interno della classe ...
Aconcagua

5
@Peter Questo è particolarmente interessante in scenari in cui non C ++ 11 è disponibile - e questi fanno esistono ancora oggi (in realtà io sono in un progetto di avere a che fare con, e mi manca più o meno alcune delle nuove funzionalità ... ).
Aconcagua,

Risposte:


161

Il motivo std::string(0)è valido, è dovuto al 0fatto che è una costante puntatore null. Quindi 0 corrisponde al costruttore della stringa che prende un puntatore. Quindi il codice esegue la condizione preliminare a cui non si può passare un puntatore null std::string.

Solo il valore letterale 0verrebbe interpretato come una costante puntatore null, se fosse un valore di runtime in un non intsi avrebbe questo problema (perché invece la risoluzione del sovraccarico sarebbe invece alla ricerca di una intconversione). Né è letteralmente 1un problema, perché 1non è una costante puntatore null.

Poiché si tratta di un problema di compilazione (valori letterali non validi) è possibile rilevarlo in fase di compilazione. Aggiungi un sovraccarico di questo modulo:

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

std::nullptr_tè il tipo di nullptr. E sarà corrisponde alcun puntatore costante nullo, sia esso 0, 0ULLo nullptr. E poiché la funzione viene eliminata, causerà un errore di tempo di compilazione durante la risoluzione del sovraccarico.


Questa è di gran lunga la soluzione migliore, completamente dimenticata che posso sovraccaricare un puntatore NULL.
Kabanus,

in Visual Studio, anche "ohNo [0]" genera un'eccezione di valore null. Significa un'implementazione specifica della classe std :: string?
TruthSeeker,

@pmp Ciò che viene lanciato (se non altro) è specifico dell'implementazione, ma il punto è che la stringa è un puntatore NULL in tutti loro. Con questa soluzione non arriverai alla parte di eccezione, verrà rilevata al momento della compilazione.
Kabanus,

18
@pmp - Lo std::stringstandard C ++ non consente il passaggio di un puntatore null al costruttore. È un comportamento indefinito, quindi MSVC può fare quello che vuole (come lanciare un'eccezione).
StoryTeller - Unslander Monica,

26

Un'opzione è dichiarare un privatesovraccarico operator[]()che accetta un argomento integrale e non definirlo.

Questa opzione funzionerà con tutti gli standard C ++ (1998 in poi), a differenza di opzioni come void operator[](std::nullptr_t) = deletequelle valide da C ++ 11.

La creazione di operator[]()un privatemembro causerà un errore diagnosticabile sul tuo esempio ohNo[0], a meno che quell'espressione non sia utilizzata da una funzione membro o frienddella classe.

Se quell'espressione viene utilizzata da una funzione membro o frienddella classe, il codice verrà compilato ma, poiché la funzione non è definita, in genere la compilazione non riuscirà (ad esempio un errore del linker dovuto a una funzione non definita).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.