Domanda

Cosa significa la parola chiave esplicita in C ++?

È stato utile?

Soluzione

Al compilatore è consentito eseguire una conversione implicita per risolvere i parametri in una funzione. Ciò significa che il compilatore può utilizzare i costruttori richiamabili con un parametro singolo per convertire da un tipo a un altro al fine di ottenere il tipo giusto per un parametro.

Ecco una classe di esempio con un costruttore che può essere utilizzata per conversioni implicite:

class Foo
{
public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo) : m_foo (foo) 
  {
  }

  int GetFoo () { return m_foo; }

private:
  int m_foo;
};

Ecco una semplice funzione che accetta un oggetto Foo :

void DoBar (Foo foo)
{
  int i = foo.GetFoo ();
}

ed ecco dove viene chiamata la funzione DoBar .

int main ()
{
  DoBar (42);
}

L'argomento non è un oggetto Foo , ma un int . Tuttavia, esiste un costruttore per Foo che accetta un int , quindi questo costruttore può essere utilizzato per convertire il parametro nel tipo corretto.

Il compilatore può eseguire questa operazione una volta per ciascun parametro.

Il prefisso della parola chiave esplicita al costruttore impedisce al compilatore di utilizzare quel costruttore per conversioni implicite. Aggiungendolo alla classe precedente si creerà un errore del compilatore nella chiamata di funzione DoBar (42) . È ora necessario richiedere esplicitamente la conversione con DoBar (Foo (42))

Il motivo per cui potresti voler fare questo è evitare la costruzione accidentale che può nascondere bug. Esempio inventato:

  • Hai una classe MyString (int size) con un costruttore che costruisce una stringa delle dimensioni indicate. Hai una funzione print (const MyString & amp;) e chiami print (3) (quando effettivamente intendevi chiamare print (" 3 ") ). Ti aspetti che stampi "3", ma stampa invece una stringa vuota di lunghezza 3.

Altri suggerimenti

Supponi di avere una classe String :

class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

Ora, se provi:

String mystring = 'x';

Il carattere 'x' verrà convertito implicitamente in int e quindi verrà chiamato il costruttore String (int) . Ma questo non è ciò che l'utente avrebbe potuto intendere. Quindi, per evitare tali condizioni, definiremo il costruttore come esplicito :

class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};

In C ++, un costruttore con un solo parametro richiesto è considerato una funzione di conversione implicita. Converte il tipo di parametro nel tipo di classe. Se questa è una cosa positiva o no dipende dalla semantica del costruttore.

Ad esempio, se hai una classe di stringhe con il costruttore String (const char * s) , probabilmente è esattamente quello che vuoi. Puoi passare un const char * a una funzione che si aspetta un String e il compilatore costruirà automaticamente un oggetto temporaneo String per te.

D'altra parte, se si dispone di una classe di buffer il cui costruttore Buffer (int size) accetta la dimensione del buffer in byte, probabilmente non si desidera che il compilatore giri tranquillamente int s in Buffer s. Per evitarlo, dichiari il costruttore con la parola chiave esplicita :

class Buffer { explicit Buffer(int size); ... }

In questo modo,

void useBuffer(Buffer& buf);
useBuffer(4);

diventa un errore di compilazione. Se vuoi passare un oggetto temporaneo Buffer , devi farlo esplicitamente:

useBuffer(Buffer(4));

In breve, se il costruttore a parametro singolo converte il parametro in un oggetto della tua classe, probabilmente non vuoi usare la parola chiave esplicita . Ma se hai un costruttore che semplicemente accetta un singolo parametro, dovresti dichiararlo come esplicito per evitare che il compilatore ti sorprenda con conversioni inaspettate.

Questa risposta riguarda la creazione di oggetti con / senza un costruttore esplicito poiché non è trattata nelle altre risposte.

Considera la seguente classe senza un costruttore esplicito:

class Foo
{
public:
    Foo(int x) : m_x(x)
    {
    }

private:
    int m_x;
};

Gli oggetti della classe Foo possono essere creati in 2 modi:

Foo bar1(10);

Foo bar2 = 20;

A seconda dell'implementazione, il secondo modo di creare un'istanza della classe Foo può essere fonte di confusione, o meno ciò che il programmatore intendeva. Il prefisso della parola chiave esplicito al costruttore genererebbe un errore del compilatore in Foo bar2 = 20; .

È di solito buona pratica dichiarare i costruttori a argomento singolo come esplicito , a meno che l'implementazione non lo proibisca espressamente.

Nota anche che i costruttori con

  • argomenti predefiniti per tutti i parametri o
  • argomenti predefiniti per il secondo parametro in poi

possono entrambi essere usati come costruttori a argomento singolo. Quindi potresti voler rendere anche questi espliciti .

Un esempio in cui si desidera deliberatamente non voler rendere esplicito il costruttore a argomento singolo è se si sta creando un funzione (vedere la struttura "add_x" dichiarata in questa risposta). In tal caso, creare un oggetto come add_x add30 = 30; probabilmente avrebbe senso.

Qui è una buona scrittura- su costruttori espliciti.

La parola chiave esplicita trasforma un costruttore di conversione in costruttore non di conversione. Di conseguenza, il codice è meno soggetto a errori.

La parola chiave esplicita accompagna

  • un costruttore di classe X che non può essere utilizzato per convertire implicitamente il primo (qualsiasi) parametro in tipo X
  

C ++ [class.conv.ctor]

     

1) Un costruttore dichiarato senza l'identificatore di funzione esplicito specifica una conversione dai tipi dei suoi parametri al tipo della sua classe. Tale costruttore è chiamato costruttore di conversione.

     

2) Un costruttore esplicito costruisce oggetti proprio come i costruttori non espliciti, ma lo fa solo dove la sintassi di inizializzazione diretta (8.5) o dove i cast (5.2.9, 5.4) sono esplicitamente usati. Un costruttore predefinito può essere un costruttore esplicito; tale costruttore verrà utilizzato per eseguire l'inizializzazione predefinita o l'inizializzazione del valore   (8,5).

  • o una funzione di conversione considerata solo per l'inizializzazione diretta e la conversione esplicita.
  

C ++ [class.conv.fct]

     

2) Una funzione di conversione può essere esplicita (7.1.2), nel qual caso viene considerata solo una conversione definita dall'utente per l'inizializzazione diretta (8.5). In caso contrario, le conversioni definite dall'utente non sono limitate all'uso nelle assegnazioni   e inizializzazioni.

Panoramica

Le funzioni di conversione e i costruttori espliciti possono essere utilizzati solo per conversioni esplicite (inizializzazione diretta o operazione di cast esplicito) mentre i costruttori e le funzioni di conversione non espliciti possono essere utilizzati sia per conversioni implicite che esplicite.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Esempio di utilizzo delle strutture X, Y, Z e funzioni foo, bar, baz :

Diamo un'occhiata a una piccola configurazione di strutture e funzioni per vedere la differenza tra conversioni esplicite e non esplicite .

struct Z { };

struct X { 
  explicit X(int a); // X can be constructed from int explicitly
  explicit operator Z (); // X can be converted to Z explicitly
};

struct Y{
  Y(int a); // int can be implicitly converted to Y
  operator Z (); // Y can be implicitly converted to Z
};

void foo(X x) { }
void bar(Y y) { }
void baz(Z z) { }

Esempi riguardanti il ??costruttore:

Conversione di un argomento di funzione:

foo(2);                     // error: no implicit conversion int to X possible
foo(X(2));                  // OK: direct initialization: explicit conversion
foo(static_cast<X>(2));     // OK: explicit conversion

bar(2);                     // OK: implicit conversion via Y(int) 
bar(Y(2));                  // OK: direct initialization
bar(static_cast<Y>(2));     // OK: explicit conversion

Inizializzazione oggetto:

X x2 = 2;                   // error: no implicit conversion int to X possible
X x3(2);                    // OK: direct initialization
X x4 = X(2);                // OK: direct initialization
X x5 = static_cast<X>(2);   // OK: explicit conversion 

Y y2 = 2;                   // OK: implicit conversion via Y(int)
Y y3(2);                    // OK: direct initialization
Y y4 = Y(2);                // OK: direct initialization
Y y5 = static_cast<Y>(2);   // OK: explicit conversion

Esempi relativi alle funzioni di conversione:

X x1{ 0 };
Y y1{ 0 };

Conversione di un argomento di funzione:

baz(x1);                    // error: X not implicitly convertible to Z
baz(Z(x1));                 // OK: explicit initialization
baz(static_cast<Z>(x1));    // OK: explicit conversion

baz(y1);                    // OK: implicit conversion via Y::operator Z()
baz(Z(y1));                 // OK: direct initialization
baz(static_cast<Z>(y1));    // OK: explicit conversion

Inizializzazione oggetto:

Z z1 = x1;                  // error: X not implicitly convertible to Z
Z z2(x1);                   // OK: explicit initialization
Z z3 = Z(x1);               // OK: explicit initialization
Z z4 = static_cast<Z>(x1);  // OK: explicit conversion

Z z1 = y1;                  // OK: implicit conversion via Y::operator Z()
Z z2(y1);                   // OK: direct initialization
Z z3 = Z(y1);               // OK: direct initialization
Z z4 = static_cast<Z>(y1);  // OK: explicit conversion

Perché usare le funzioni o i costruttori di conversione esplicita ?

I costruttori di conversione e le funzioni di conversione non esplicite possono introdurre ambiguità.

Considera una struttura V , convertibile in int , una struttura U implicitamente costruibile da V e una funzione f sovraccaricato rispettivamente per U e bool .

struct V {
  operator bool() const { return true; }
};

struct U { U(V) { } };

void f(U) { }
void f(bool) {  }

Una chiamata a f è ambigua se si passa un oggetto di tipo V .

V x;
f(x);  // error: call of overloaded 'f(V&)' is ambiguous

Il compilatore non sa se usare il costruttore di U o la funzione di conversione per convertire l'oggetto V in un tipo per passare a f .

Se il costruttore di U o la funzione di conversione di V sarebbe esplicito , non ci sarebbe ambiguità poiché solo il non- la conversione esplicita sarebbe considerata. Se entrambi sono espliciti, la chiamata a f utilizzando un oggetto di tipo V dovrebbe essere effettuata utilizzando una conversione esplicita o un'operazione di cast.

I costruttori di conversione e le funzioni di conversione non esplicite possono portare a comportamenti imprevisti.

Considera una funzione che stampa un vettore:

void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; }

Se il costruttore della dimensione del vettore non fosse esplicito, sarebbe possibile chiamare la funzione in questo modo:

print_intvector(3);

Cosa ci si aspetterebbe da una simile chiamata? Una riga contenente 3 o tre righe contenenti 0 ? (Dove il secondo è quello che succede.)

L'uso della parola chiave esplicita in un'interfaccia di classe impone all'utente dell'interfaccia di essere esplicito su una conversione desiderata.

Come dice Bjarne Stroustrup (in " The C ++ Programming Language " ;, 4th Ed., 35.2.1, pp. 1011) sulla domanda perché std :: duration non può essere implicitamente costruito da un numero semplice:

  

Se sai cosa intendi, sii esplicito al riguardo.

Costruttori di conversioni esplicite (solo C ++)

  

L'identificatore di funzione esplicita controlla il tipo implicito indesiderato   conversioni. Può essere utilizzato solo nelle dichiarazioni dei costruttori   all'interno di una dichiarazione di classe. Ad esempio, ad eccezione del valore predefinito   costruttore, i costruttori nella seguente classe sono la conversione   costruttori.

class A
{
public:
    A();
    A(int);
    A(const char*, int = 0);
};

Le seguenti dichiarazioni sono legali:

A c = 1;
A d = "Venditti";

La prima dichiarazione equivale a A c = A (1); .

Se dichiari il costruttore della classe come esplicito , le dichiarazioni precedenti sarebbero illegali.

Ad esempio, se dichiari la classe come:

class A
{
public:
    explicit A();
    explicit A(int);
    explicit A(const char*, int = 0);
};

Puoi assegnare solo valori che corrispondono ai valori del tipo di classe.

Ad esempio, le seguenti dichiarazioni sono legali:

  A a1;
  A a2 = A(1);
  A a3(1);
  A a4 = A("Venditti");
  A* p = new A(1);
  A a5 = (A)1;
  A a6 = static_cast<A>(1);

La parola chiave esplicito può essere utilizzata per imporre a un costruttore di essere chiamato esplicitamente .

class C{
public:
    explicit C(void) = default;
};

int main(void){
    C c();
    return 0;
}

la esplicita -keyword davanti al costruttore C (void) indica al compilatore che è consentita solo la chiamata esplicita a questo costruttore.

La parola chiave esplicita può essere utilizzata anche negli operatori cast di tipo definito dall'utente:

class C{
public:
    explicit inline operator bool(void) const{
        return true;
    }
};

int main(void){
    C c;
    bool b = static_cast<bool>(c);
    return 0;
}

Qui, esplicito -keyword impone che solo i cast espliciti siano validi, quindi bool b = c; sarebbe un cast non valido in questo caso. In situazioni come queste esplicite , la parola chiave può aiutare il programmatore a evitare lanci impliciti e non intenzionali. Questo utilizzo è stato standardizzato in C ++ 11 .

Il riferimento Cpp è sempre utile !!! I dettagli sullo specificatore esplicito sono disponibili qui . Potrebbe essere necessario esaminare conversioni implicite e inizializzazione copia .

Occhiata veloce

  

L'identificatore esplicito specifica che un costruttore o una funzione di conversione (dal C ++ 11) non consente conversioni implicite o inizializzazione della copia.

Esempio come segue:

struct A
{
    A(int) { }      // converting constructor
    A(int, int) { } // converting constructor (C++11)
    operator bool() const { return true; }
};

struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};

int main()
{
    A a1 = 1;      // OK: copy-initialization selects A::A(int)
    A a2(2);       // OK: direct-initialization selects A::A(int)
    A a3 {4, 5};   // OK: direct-list-initialization selects A::A(int, int)
    A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
    A a5 = (A)1;   // OK: explicit cast performs static_cast
    if (a1) cout << "true" << endl; // OK: A::operator bool()
    bool na1 = a1; // OK: copy-initialization selects A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization

//  B b1 = 1;      // error: copy-initialization does not consider B::B(int)
    B b2(2);       // OK: direct-initialization selects B::B(int)
    B b3 {4, 5};   // OK: direct-list-initialization selects B::B(int, int)
//  B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
    B b5 = (B)1;   // OK: explicit cast performs static_cast
    if (b5) cout << "true" << endl; // OK: B::operator bool()
//  bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}

Questo è già stato discusso ( cos'è il costruttore esplicito ). Ma devo dire che manca le descrizioni dettagliate trovate qui.

Inoltre, è sempre una buona pratica di codifica creare costruttori di un argomento (inclusi quelli con valori predefiniti per arg2, arg3, ...) come già detto. Come sempre con C ++: in caso contrario, ti augurerai di averlo fatto ...

Un'altra buona pratica per le classi è quella di rendere privata la costruzione e l'assegnazione delle copie (a.k.a. disabilitarlo) a meno che non sia necessario implementarlo. Ciò evita di avere eventuali copie dei puntatori quando si utilizzano i metodi che C ++ creerà per impostazione predefinita. Un altro modo per farlo è derivare da boost :: non copiabile.

I costruttori aggiungono la conversione implicita. Per sopprimere questa conversione implicita è necessario dichiarare un costruttore con un parametro esplicito.

In C ++ 11 puoi anche specificare un tipo di operatore " () " con tale parola chiave http://en.cppreference.com/w/cpp/language/explicit Con tale specifica è possibile utilizzare l'operatore in termini di conversioni esplicite e inizializzazione diretta dell'oggetto.

P.S. Quando si utilizzano trasformazioni definite DA UTENTE (tramite costruttori e operatore di conversione del tipo) è consentito solo un livello di conversioni implicite utilizzate. Ma puoi combinare queste conversioni con conversioni in altre lingue

  • rango integrale (carattere in int, valore float in doppio);
  • conversioni standart (int per raddoppiare);
  • converte i puntatori di oggetti in classe base e in void *;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top