explicit
ha lo scopo di impedire conversioni implicite. Ogni volta che si utilizza qualcosa come String(foo);
, si tratta di una conversione esplicita, quindi l'utilizzo di explicit
non cambierà in caso di esito positivo o negativo.
Pertanto, diamo un'occhiata a uno scenario che comporta la conversione implicita. Cominciamo con la tua String
classe:
class String {
public:
explicit String(int n); // allocate n bytes to the String object
String(const char *p); // initializes object with char *p
};
Poi definiamo una funzione che riceve un parametro di tipo String
(potrebbe anche essere String const &
, ma String
farà per il momento):
int f(String);
I suoi costruttori consentire la conversione implicita da char const *
, ma solo la conversione esplicita da int
. Ciò significa che se chiamo:
f("this is a string");
... il compilatore genererà il codice per costruire un oggetto stringa dalla stringa letterale, e poi chiamo f
con quella String
oggetto.
Se, tuttavia, si tenta di chiamare:
f(2);
fallirà, poiché il costruttore String
che accetta un parametro int
è stata segnata explicit
.Ciò significa che se voglio convertire un int
ad un String
, devo farlo in modo esplicito:
f(String(2));
Se il costruttore String(char const *);
furono segnati anche explicit
, allora non sarebbe in grado di chiamare f("this is a string")
sia - si 'd devono utilizzare f(String("this is a string"));
si noti, tuttavia, che controlla solo explicit
conversione implicita da un certo tipo foo
al tipo definito. Non ha alcun effetto sulla conversione implicita da altro tipo a il tipo utilizzato dal costruttore explicit
. Quindi, il tuo esplicito costruttore che prende digitare int
vorrà ancora un parametro in virgola mobile:
f(String(1.2))
... perché questo comporta una conversione implicita da double
a int
seguita da una conversione esplicita da int
a String
. Se si vuole impedire una conversione double
-String
, lo faresti per (ad esempio) che fornisce un costruttore di overload che prende un double
, ma poi getta:
String(double) { throw("Conversion from double not allowed"); }
Ora la conversione implicita da double
a int
non accadrà - lo double
verrà passato direttamente al tuo ctor senza conversione.
Quanto a quello che utilizza explicit
compie: il punto principale di usare explicit
è quello di evitare che codice da compilare che altrimenti la compilazione. Se combinato con il sovraccarico, le conversioni implicite possono portare ad alcune selezioni piuttosto strane a volte.
È più semplice dimostrare un problema con gli operatori di conversione piuttosto che con i costruttori (perché è possibile farlo solo con una classe). Per esempio, prendiamo in considerazione una piccola classe string abbastanza simile a un sacco che sono stati scritti prima che la gente intesa come molto su come problematica implicita conversioni possono essere:
class Foo {
std::string data;
public:
Foo(char const *s) : data(s) { }
Foo operator+(Foo const &other) { return (data + other.data).c_str(); }
operator char const *() { return data.c_str(); }
};
(ho tradito utilizzando std::string
per memorizzare i dati, ma lo stesso sarebbe vero se avessi fatto come loro e memorizzato un char *
, e usato new
per allocare la memoria).
Ora, questo rende le cose come queste funzionano bene:
Foo a("a");
Foo b("b");
std::cout << a + b;
... e, (ovviamente) il risultato è che esso stampa ab
. Ma cosa succede se l'utente commette un errore minore e digita -
dove intendeva digitare +
?
Ecco dove le cose si fanno brutte - il codice è ancora compilato e "funziona" (per qualche definizione della parola), ma stampa assurdità. In un rapido test sulla mia macchina, ho ottenuto -24
, ma non contano di duplicare quel particolare risultato.
Il problema qui deriva dal consentire la conversione implicita da String
a char *
. Quando proviamo a sottrarre i due oggetti String
, il compilatore prova a capire cosa intendessimo.Poiché non è in grado di sottrarli direttamente, verifica se è possibile convertirli in un tipo che supporta la sottrazione e, in effetti, char const *
supporta la sottrazione, quindi converte entrambi i nostri oggetti String
in char const *
, quindi sottrae i due puntatori.
Se vi segnalo che la conversione come explicit
invece:
explicit operator char const *() { return data.c_str(); }
... il codice che tenta di sottrarre due String
oggetti semplicemente non verrà compilato.
La stessa idea di base può/si applica ai costruttori explicit
, ma il codice per dimostrarlo si allunga perché generalmente sono necessari almeno un paio di classi diverse.
Ti aspetti 'String ('x')' per chiamare 'String (const char *)'? ''x'' è un' char', non un puntatore 'char'. Ad ogni modo, stai mostrando solo costruzioni * esplicite * di 'String', quindi' explicit' non fa differenza. Prova a chiamare una funzione che accetta un 'String' con un' int', e non funzionerà, perché la conversione 'int' →' String' non può essere resa * implicitamente *. – Biffen
'String mystring = 'x';' [fa _non_ work] (http://coliru.stacked-crooked.com/a/79af8ce27adf8a61). – cpplearner
@cpplearner Sono consapevole di ciò, ecco perché ho indicato che non uso la parola esplicita. Nel tuo esempio, stai usando esplicito –