c++ aprenda - ¿Qué significa la palabra clave explícita?




avanzado libro (10)

¿Qué significa la palabra clave explicit en C ++?


Answers

Supongamos que tienes una String clase:

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

Ahora, si lo intentas:

String mystring = 'x';

El carácter 'x' se convertirá implícitamente a int y luego se llamará al constructor String(int) . Pero, esto no es lo que el usuario podría haber querido. Entonces, para evitar tales condiciones, definiremos el constructor como explicit :

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

La palabra clave explicit acompaña a cualquiera

  • un constructor de clase X que no se puede usar para convertir implícitamente el primer parámetro (solo el único) al tipo X

C ++ [class.conv.ctor]

1) Un constructor declarado sin el especificador de función explícito especifica una conversión de los tipos de sus parámetros al tipo de su clase. Tal constructor se llama un constructor de conversión.

2) Un constructor explícito construye objetos como constructores no explícitos, pero lo hace solo cuando se usa explícitamente la sintaxis de inicialización directa (8.5) o donde las conversiones (5.2.9, 5.4) se usan explícitamente. Un constructor predeterminado puede ser un constructor explícito; dicho constructor se utilizará para realizar la inicialización predeterminada o la inicialización de valores (8.5).

  • o una función de conversión que solo se considera para la inicialización directa y la conversión explícita.

C ++ [class.conv.fct]

2) Una función de conversión puede ser explícita (7.1.2), en cuyo caso solo se considera una conversión definida por el usuario para la inicialización directa (8.5). De lo contrario, las conversiones definidas por el usuario no están restringidas para usarse en asignaciones e inicializaciones.

Visión general

Las funciones de conversión explícita y los constructores solo pueden usarse para conversiones explícitas (inicialización directa o operación de conversión explícita), mientras que los constructores no explícitos y las funciones de conversión pueden usarse para conversiones implícitas y explícitas.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Ejemplo usando estructuras X, Y, Z y funciones foo, bar, baz :

Veamos una pequeña configuración de estructuras y funciones para ver la diferencia entre conversiones explicit y no 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) { }

Ejemplos de constructor:

Conversión de un argumento de función:

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

Inicialización de objetos:

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

Ejemplos con respecto a las funciones de conversión:

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

Conversión de un argumento de función:

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

Inicialización de objetos:

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

¿Por qué usar funciones de conversión explicit o constructores?

Los constructores de conversión y las funciones de conversión no explícita pueden introducir ambigüedad.

Considere una estructura V , convertible a int , una estructura U construible implícitamente a partir de V y una función f sobrecargada para U y bool respectivamente.

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

struct U { U(V) { } };

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

Una llamada a f es ambigua si pasa un objeto de tipo V

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

El compilador no sabe si usar el constructor de U o la función de conversión para convertir el objeto V en un tipo para pasar a f .

Si el constructor de U o la función de conversión de V serían explicit , no habría ambigüedad ya que solo se consideraría la conversión no explícita. Si ambos son explícitos, la llamada a f usando un objeto de tipo V tendría que hacerse usando una conversión explícita u operación de conversión.

Los constructores de conversión y las funciones de conversión no explícita pueden llevar a un comportamiento inesperado.

Considera una función imprimiendo algún vector:

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

Si el constructor de tamaño del vector no fuera explícito, sería posible llamar a la función así:

print_intvector(3);

¿Qué se esperaría de tal llamada? ¿Una línea que contiene 3 o tres líneas que contienen 0 ? (Donde el segundo es lo que pasa.)

El uso de la palabra clave explícita en una interfaz de clase obliga al usuario de la interfaz a ser explícito acerca de una conversión deseada.

Como dice Bjarne Stroustrup (en "The C ++ Programming Language", 4a ed., 35.2.1, pp. 1011) sobre la pregunta de por qué std::duration no puede construirse implícitamente a partir de un número simple:

Si sabes lo que quieres decir, sé explícito al respecto.


El compilador tiene permitido hacer una conversión implícita para resolver los parámetros de una función. Lo que esto significa es que el compilador puede usar constructores con un solo parámetro para convertir de un tipo a otro para obtener el tipo correcto para un parámetro.

Aquí hay una clase de ejemplo con un constructor que se puede usar para conversiones implícitas:

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

Aquí hay una función simple que toma un objeto Foo :

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

y aquí es donde se DoBar función DoBar .

int main ()
{
  DoBar (42);
}

El argumento no es un objeto Foo , sino un int . Sin embargo, existe un constructor para Foo que toma un int por lo que este constructor se puede usar para convertir el parámetro al tipo correcto.

El compilador tiene permitido hacer esto una vez para cada parámetro.

El prefijo de la palabra clave explicit al constructor evita que el compilador use ese constructor para las conversiones implícitas. Al agregarlo a la clase anterior se creará un error de compilación en la función llamada DoBar (42) . Ahora es necesario solicitar la conversión explícitamente con DoBar (Foo (42))

La razón por la que podría querer hacer esto es evitar una construcción accidental que pueda ocultar errores. Ejemplo elaborado:

  • Tienes una MyString(int size) con un constructor que construye una cadena del tamaño dado. Tiene una función de print(const MyString&) y llama a print(3) (cuando en realidad tenía la intención de llamar a print("3") ). Usted espera que imprima "3", pero en su lugar imprime una cadena vacía de longitud 3.

Esto ya ha sido discutido ( lo que es constructor explícito ). Pero debo decir, que carece de las descripciones detalladas que se encuentran aquí.

Además, siempre es una buena práctica de codificación hacer que los argumentos de un solo argumento (incluidos aquellos con valores predeterminados para arg2, arg3, ...) como ya se ha indicado. Como siempre con C ++: si no lo haces, desearás haberlo hecho ...

Otra buena práctica para las clases es hacer que la construcción y la asignación de copias sean privadas (también deshabilitadas) a menos que realmente necesite implementarla. Esto evita tener copias eventuales de punteros cuando se usan los métodos que C ++ creará por usted de manera predeterminada. Otra forma de hacer esto es derivar de boost :: noncopyable.


Constructores de conversión explícita (solo C ++)

El especificador de funciones explícitas controla las conversiones de tipos implícitas no deseadas. Solo se puede utilizar en declaraciones de constructores dentro de una declaración de clase. Por ejemplo, a excepción del constructor predeterminado, los constructores en la siguiente clase son constructores de conversión.

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

Las siguientes declaraciones son legales:

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

La primera declaración es equivalente a A c = A( 1 ); .

Si declara el constructor de la clase como explicit , las declaraciones anteriores serían ilegales.

Por ejemplo, si declara la clase como:

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

Solo puede asignar valores que coincidan con los valores del tipo de clase.

Por ejemplo, las siguientes afirmaciones son legales:

  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 palabra clave explicit se puede usar para hacer que un constructor se llame explícitamente .

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

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

la palabra clave explicit delante del constructor C(void) le dice al compilador que solo se permite la llamada explícita a este constructor.

La palabra clave explicit también se puede utilizar en operadores de conversión de tipos definidos por el usuario:

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

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

Aquí, explicit -keyword impone que las conversiones explícitas sean válidas, entonces bool b = c; Sería un reparto inválido en este caso. En situaciones como estas, explicit palabra clave explicit puede ayudar al programador a evitar conversiones implícitas e involuntarias. Este uso ha sido estandarizado en C++11 .


Referencia de Cpp siempre es útil !!! Los detalles sobre el especificador explícito se pueden encontrar here . Es posible que también deba considerar las conversiones implícitas y copy-initialization .

Vistazo rápido

El especificador explícito especifica que un constructor o una función de conversión (desde C ++ 11) no permite conversiones implícitas o inicialización de copias.

Ejemplo como sigue:

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
}

La palabra clave explicit hace un constructor de conversión a un constructor sin conversión. Como resultado, el código es menos propenso a errores.


Esta respuesta trata sobre la creación de objetos con / sin un constructor explícito, ya que no está cubierto en las otras respuestas.

Considere la siguiente clase sin un constructor explícito:

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

private:
    int m_x;
};

Los objetos de la clase Foo se pueden crear de 2 maneras:

Foo bar1(10);

Foo bar2 = 20;

Dependiendo de la implementación, la segunda forma de instanciar la clase Foo puede ser confusa, o no lo que pretendía el programador. El prefijo de la palabra clave explicit al constructor generaría un error de compilación en Foo bar2 = 20; .

Por lo general, es una buena práctica declarar los constructores de un solo argumento como explicit , a menos que su implementación lo prohíba específicamente.

Tenga en cuenta también que los constructores con

  • argumentos predeterminados para todos los parámetros, o
  • Argumentos por defecto para el segundo parámetro en adelante.

ambos pueden ser utilizados como constructores de un solo argumento. Así que es posible que desee hacer estos también explicit .

Un ejemplo en el que no querría hacer explícito su constructor de argumento único es si está creando un functor (consulte la estructura 'add_x' declarada en this respuesta). En tal caso, creando un objeto como add_x add30 = 30; probablemente tendría sentido

Here hay una buena reseña de constructores explícitos.


¿Responde this tu pregunta?

Nunca he usado reinterpret_cast , y me pregunto si encontrar un caso que lo necesite no es un olor a mal diseño. En el código base trabajo en dynamic_cast se usa mucho. La diferencia con static_cast es que dynamic_cast realiza una comprobación en tiempo de ejecución que puede (más seguro) o no (más sobrecarga) ser lo que desea (ver msdn ).





c++ constructor explicit c++-faq explicit-constructor