[c++] ¿Qué significa la palabra clave explícita?



Answers

Supongamos que tiene una clase String :

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

Ahora, si 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 intentado. 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
};
Question

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




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 manera de instanciar la clase Foo puede ser confusa, o no, lo que el programador pretendía. La prefijación 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 argumento único como explicit , a menos que su implementación lo prohíba específicamente.

Tenga en cuenta también que los constructores con

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

ambos pueden usarse como constructores de argumento único. Por lo tanto, es posible que desee hacer que estos también sean explicit .

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

Here hay un buen artículo sobre constructores explícitos.




Esto ya se ha discutido ( lo que es un 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 constructores de un argumento (incluidos aquellos con valores predeterminados para arg2, arg3, ...) ya se hayan 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 (es decir, deshabilitarlas) a menos que realmente necesites implementarlas. Esto evita tener copias eventuales de punteros al usar los métodos que C ++ creará para usted de manera predeterminada. Otra forma de hacer esto es derivar de boost :: noncopyable.




La palabra clave explicit acompaña a cualquiera

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

C ++ [class.conv.ctor]

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

2) Un constructor explícito construye objetos al igual que los constructores no explícitos, pero solo lo hace cuando se usan explícitamente la sintaxis de inicialización directa (8.5) o los moldes (5.2.9, 5.4). Un constructor predeterminado puede ser un constructor explícito; dicho constructor se usará para realizar la inicialización por defecto o la inicialización de valor (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 al uso en asignaciones e inicializaciones.

Visión de conjunto

Las funciones de conversión explícita y los constructores solo se pueden usar para conversiones explícitas (inicialización directa u operación de conversión explícita) mientras que los constructores no explícitos y las funciones de conversión se pueden usar 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 explicit conversiones explicit y las 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 con respecto al 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 de 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ícitas pueden introducir ambigüedad.

Considere una estructura V , convertible a int , una estructura U implícitamente construible 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 fueran 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 o una operación de conversión.

Los constructores de conversión y las funciones de conversión no explícitas pueden llevar a comportamientos inesperados.

Considere 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 tamaño-constructor del vector no fuera explícito, sería posible llamar a la función de esta manera:

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

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

Como dice Bjarne Stroustrup (en "The C ++ Programming Language", 4th 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.




Los constructores añaden conversión implícita. Para suprimir esta conversión implícita, se requiere declarar un constructor con un parámetro explícito.

En C ++ 11 también puede especificar un "tipo de operador ()" con dicha palabra clave here Con dicha especificación puede usar operador en términos de conversiones explícitas, y inicialización directa del objeto.

PS Al usar las transformaciones definidas POR USUARIO (a través de constructores y operador de conversión de tipo), solo se permite un nivel de conversiones implícitas. Pero puede combinar estas conversiones con otras conversiones de idiomas

  • rangos integrales (char a int, float a double);
  • conversiones standart (int a doble);
  • convertir punteros de objetos a la clase base y anular *;



Links