riferimento - static c++




Cosa significa la parola chiave esplicita? (8)

Cosa significa la parola chiave explicit in C ++?


La parola chiave explicit accompagna entrambi

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

C ++ [class.conv.ctor]

1) Un costruttore dichiarato senza specificatore della funzione esplicita 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 costruttori non espliciti, ma lo fa solo dove la sintassi di inizializzazione diretta (8.5) o dove 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 di default o l'inizializzazione del valore (8.5).

  • o una funzione di conversione che viene 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). Altrimenti, le conversioni definite dall'utente non sono limitate all'utilizzo in assegnazioni e inizializzazioni.

Panoramica

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

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Esempio utilizzando le strutture X, Y, Z e le funzioni foo, bar, baz :

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

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 relativi al 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 dell'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 dell'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é utilizzare funzioni di conversione o costruttori explicit ?

Costruttori di conversione e funzioni di conversione non esplicita possono introdurre ambiguità.

Si consideri una struttura V , convertibile in int , una struttura U implicitamente costruibile da V e una funzione f sovraccarica rispettivamente di U e di bool .

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

struct U { U(V) { } };

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

Una chiamata a f è ambigua se 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 fossero explicit , non ci sarebbe ambiguità poiché sarebbe considerata solo la conversione non esplicita. Se entrambi sono espliciti, la chiamata a f utilizzando un oggetto di tipo V dovrebbe essere eseguita utilizzando una conversione esplicita o un'operazione di cast.

I costruttori di conversione e le funzioni di conversione non esplicita 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 tale chiamata? Una riga contenente 3 o tre righe contenenti 0 ? (Dove il secondo è ciò che accade.)

L'uso della parola chiave esplicita in un'interfaccia di classe fa sì che l'utente dell'interfaccia sia esplicito riguardo a una conversione desiderata.

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

Se sai cosa intendi, sii esplicito a riguardo.


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

In C ++ 11 è anche possibile specificare un "tipo di operatore ()" con tale parola chiave here Con tali specifiche è possibile utilizzare l'operatore in termini di conversioni esplicite e inizializzazione diretta dell'oggetto.

PS Quando si utilizzano le trasformazioni definite da USER (tramite costruttori e operatore di conversione del tipo) è consentito un solo livello di conversioni implicite utilizzate. Ma puoi combinare queste conversioni con altre conversioni linguistiche

  • su ranghi integrali (char to int, float to double);
  • conversioni standard (int per raddoppiare);
  • convertire puntatori di oggetti in classe base e annullare *;

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 buona o meno dipende dalla semantica del costruttore.

Ad esempio, se si dispone di una classe stringa con il costruttore String(const char* s) , probabilmente è esattamente ciò che si desidera. È possibile passare un const char* a una funzione che si aspetta una String e il compilatore costruirà automaticamente un oggetto String temporaneo per te.

D'altra parte, se si dispone di una classe di buffer il cui costruttore Buffer(int size) prende la dimensione del buffer in byte, probabilmente non si vuole che il compilatore ruoti tranquillamente int s in Buffer s. Per evitare ciò, dichiari il costruttore con la parola chiave explicit :

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

Quel modo,

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

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

useBuffer(Buffer(4));

In breve, se il costruttore di parametri singoli converte il parametro in un oggetto della classe, probabilmente non si desidera utilizzare la parola chiave explicit . Ma se hai un costruttore che semplicemente prende un singolo parametro, dovresti dichiararlo explicit per evitare che il compilatore ti sorprenda con conversioni inaspettate.


La parola chiave explicit fa di un costruttore di conversioni un costruttore di non conversione. Di conseguenza, il codice è meno incline agli errori.


Questa risposta riguarda la creazione di oggetti con / senza un costruttore esplicito poiché non è coperto 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 di classe Foo possono essere creati in 2 modi:

Foo bar1(10);

Foo bar2 = 20;

A seconda dell'implementazione, la seconda maniera di istanziare la classe Foo può essere confusa, o non ciò che il programmatore intendeva. Il prefisso della parola chiave explicit al costruttore generava un errore del compilatore su Foo bar2 = 20; .

In genere è buona norma dichiarare explicit costruttori a argomento singolo, a meno che l'implementazione non lo proibisca specificamente.

Si noti anche che i costruttori con

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

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

Un esempio in cui si desidera deliberatamente non rendere esplicito il costruttore del singolo argomento è se si sta creando un functor (osservare la struttura 'add_x' dichiarata in this risposta). In tal caso, creare un oggetto come add_x add30 = 30; avrebbe probabilmente un senso

Here una buona recensione su costruttori espliciti.


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

Inoltre, è sempre buona pratica di codifica creare i propri costruttori di argomenti (compresi quelli con valori predefiniti per arg2, arg3, ...) come già affermato. Come sempre con C ++: se non lo fai - ti auguro di averlo fatto ...

Un'altra buona pratica per le classi è rendere privata la costruzione e l'assegnazione delle copie (ovvero disabilitarla) a meno che non sia davvero necessario implementarla. Questo evita di avere eventuali copie di puntatori quando si usano i metodi che C ++ creerà per te di default. Un altro modo per farlo è derivato da boost :: noncopyable.


Supponi di avere una String classe:

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 potrebbe aver voluto. Pertanto, per evitare tali condizioni, definiremo explicit il costruttore:

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

Costruttori di conversioni esplicite (solo C ++)

Lo specificatore di funzione esplicita controlla le conversioni di tipo implicito indesiderate. Può essere utilizzato solo in dichiarazioni di costruttori all'interno di una dichiarazione di classe. Ad esempio, ad eccezione del costruttore predefinito, i costruttori nella seguente classe sono costruttori di conversione.

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 è equivalente a A c = A( 1 ); .

Se dichiari il costruttore della classe come explicit , 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);
};

È possibile solo assegnare 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);




explicit-constructor