universidad - libro de c++
¿Qué significa la palabra clave explícita? (11)
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.
¿Qué significa la palabra clave explicit
en C ++?
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 deprint(const MyString&)
y llama aprint(3)
(cuando en realidad tenía la intención de llamar aprint("3")
). Usted espera que imprima "3", pero en su lugar imprime una cadena vacía de longitud 3.
En C ++, un constructor con un solo parámetro requerido se considera una función de conversión implícita. Convierte el tipo de parámetro al tipo de clase. Si esto es bueno o no depende de la semántica del constructor.
Por ejemplo, si tiene una clase de cadena con String(const char* s)
constructor String(const char* s)
, eso es probablemente lo que quiere. Puede pasar un const char*
a una función que espera un String
, y el compilador construirá automáticamente un objeto String
temporal para usted.
Por otro lado, si tiene una clase de búfer cuyo Buffer(int size)
constructor Buffer(int size)
toma el tamaño del búfer en bytes, probablemente no quiera que el compilador convierta silenciosamente los int
s en Buffer
. Para evitar eso, declara el constructor con la palabra clave explicit
:
class Buffer { explicit Buffer(int size); ... }
De esa manera,
void useBuffer(Buffer& buf);
useBuffer(4);
se convierte en un error en tiempo de compilación. Si desea pasar un objeto Buffer
temporal, debe hacerlo explícitamente:
useBuffer(Buffer(4));
En resumen, si su constructor de un solo parámetro convierte el parámetro en un objeto de su clase, probablemente no quiera usar la palabra clave explicit
. Pero si tiene un constructor que simplemente toma un solo parámetro, debe declararlo como explicit
para evitar que el compilador lo sorprenda con conversiones inesperadas.
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.
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.
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.
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 .
Los constructores añaden la 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 el operador en términos de conversiones explícitas, y Inicialización directa del objeto.
PS Cuando se usan transformaciones definidas POR EL USUARIO (a través de constructores y operadores de conversión de tipos), solo se permite un nivel de conversiones implícitas. Pero puedes combinar estas conversiones con otras conversiones de idiomas.
- subir los rangos integrales (char a int, float to double);
- conversiones standart (int para duplicar);
- convertir los punteros de los objetos a la clase base y al vacío *;
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
}
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
};
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);