2011-01-26 23 views
6

primero, asegúrese de que entiende de lo que está hablando.C++ copia de inicialización e inicialización directa, el caso extraño

voy a resumir la regla aquí en primer lugar (leer norma N3225 8,5/16, 13.3.1.3, 13.3.1.4, 13.3.1.5 y),

1) Para la inicialización directa, todos los constructores se considerarán el conjunto de sobrecarga, la resolución de sobrecarga seleccionará la mejor de acuerdo con las reglas de resolución de sobrecarga.

2) Para la inicialización de copia y el tipo de fuente es igual al tipo de destino o derivado del tipo de destino, la regla es igual a la anterior excepto que solo los constructores convertidores (constructores sin explícito) se considerarán como el conjunto de sobrecarga. En realidad, esto significa que los constructores de copia/movimiento explícitos no se considerarán en el conjunto de sobrecarga.

3) Para los casos de inicialización de copia no incluidas en (2) anterior (tipo de fuente es diferente de tipo de destino y no derivadas de tipo de destino), consideramos en primer lugar secuencias de conversión definidas por el usuario que pueden convertir desde el tipo de fuente a la tipo de destino o (cuando se usa una función de conversión) a una clase derivada del mismo. Si la conversión se realiza correctamente, el resultado se utiliza para inicializar directamente el objeto de destino.

3.1) Durante esta secuencia de conversión definida por el usuario, se considerarán tanto las funciones de conversión (funciones no explícitas) como las funciones de conversión no explícitas, de acuerdo con las reglas 8.5/16 y 13.3.1.4.

3.2) El resultado prvalue inicializará directamente el objeto de destino, como las reglas enumeradas en (1), consulte 8.5/16.

Bien, suficiente para las reglas, veamos un código extraño, que realmente no tengo idea de dónde está mi razonamiento equivocado, o simplemente todos los compiladores están equivocados. Por favor, ayúdame, gracias.

struct A 
{ 
    A (int) { } 
    A() { } 
    explicit A(const A&) { } 
}; 
struct B 
{ 
    operator A() { return 2; } 
    //1) visual c++ and clang passes this 
    //gcc 4.4.3 denies this, says no viable constructor available 
}; 
int main() 
{ 
    B b; 
    A a = b; 
    //2) oops, all compilers deny this 
} 

En mi comprensión, para (1),

operator A() { return 2; } 

Debido C++ tiene una regla que función de retorno se toma como copia-inicialización, de acuerdo con la regla anterior, 2 serán en primer lugar convierte implícitamente a A, lo cual debería estar bien porque A tiene un constructor A (int). Entonces, el prvalue temporal convertido se usará para inicializar directamente el objeto devuelto, lo que también debería ser correcto, ya que la inicialización directa puede hacer uso del constructor de copia explícito. Entonces GCC está equivocado.

Para (2),

A a = b; 

En mi comprensión, en primer lugar b se convierte implícitamente a A, por el operador A(), y luego el valor convertido se utilizará para directo inicializar A, que puede por supuesto, llame al constructor de copia explícita? Por lo tanto, esto debería pasar compilación y todos los compiladores están equivocados?

Tenga en cuenta que para (2), tanto C++ visuales como clang tienen un error similar a "Error, no se puede convertir de B a A", pero si elimino la palabra clave explícita en el constructor de copia de A, el error se ha ido ..

Gracias por leer.


editar 1

Porque alguien todavía no consiguió lo que quería decir, cito el siguiente estándar de 8,5/16,

De lo contrario (es decir, para el contra copias restantes casos de inicialización), secuencias de conversión definidas por el usuario que pueden convertir del tipo de fuente a el tipo de destino o (cuando se usa una función de conversión ) a clase derivada del mismo se enumeran como se describe en 13.3.1.4, y el mejor se elige a través de la resolución de sobrecarga (13.3). Si la conversión no se puede hacer o es ambigua, la inicialización está mal formada. La función seleccionada se llama con la expresión de inicializador como su argumento ; si la función es un constructor , la llamada inicializa un temporal de la versión cv-no calificada del tipo de destino. El temporal es un valor prvero. El resultado de la llamada (que es el temporal para el caso constructor) luego se utiliza para directo initialize, de acuerdo con los reglas anteriores, el objeto que es el destino de la copia -inicialización . En ciertos casos, se permite una implementación a eliminar la copia inherente a esta inicialización directa al construir el resultado intermedio directamente en el objeto que se inicializa; ver 12.2, 12.8.

Tenga en cuenta que mencionó la inicialización directa después de la conversión definida por el usuario. Lo que significa, en mi entender, que el siguiente código obedecerá a las reglas como lo que comenté, lo que se confirma con ambos clang, coomeau en línea, C++ visual, pero GCC 4.4.3 falla tanto (1) como (2). Aunque esta es una regla extraña, pero sigue el razonamiento del estándar.

struct A 
{ 
    A (int) { } 
    A() { } 
    explicit A(const A&) { } 
}; 

int main() 
{ 
    A a = 2; //1)OK, first convert, then direct-initialize 
    A a = (A)2; //2)oops, constructor explicit, not viable here! 
} 
+0

Comeau Online acepta el fragmento de código completo tal como está escrito. –

+0

bien, gracias. Esto me da cierta confianza de que mi razonamiento al menos no va en la dirección equivocada.^_^ – user534498

+0

@James McNellis: Sin embargo, Comeau Online rechaza el código en mi respuesta, aunque según el razonamiento de OP se supone que debe ser aceptado. – AnT

Respuesta

8

Se ha declarado en su constructor de copia explicit (Por cierto, ¿por qué?), Lo que significa que ya no se puede utilizar para copiar implícito de objetos de clase. Para usar este constructor para copiar, ahora está obligado a usar la sintaxis de inicialización directa. Ver 12.3.1/2

2 Un constructor explícito construye objetos al igual que los constructores no explícitas, pero lo hace sólo cuando la sintaxis de inicialización directa (8.5) o en los moldes (5.2.9, 5.4) son de forma explícita usado.

El problema puede ilustrarse con el siguiente ejemplo mucho más corto

struct A { 
    A() {} 
    explicit A(const A&) {} 
}; 

int main() { 
    A a; 
    A b = a; // ERROR: copy-initialization 
    A c(a); // OK: direct-initialization 
} 

Esto es lo que bloquea todas las conversiones de trabajar, ya que todos ellos se basan en copia-inicialización, que a su vez se basa en copia implícita Y ha desactivado la copia implícita.

Además, consulte el Defect Report #152 que cubre este problema específico. Aunque no estoy seguro de qué se supone que son las consecuencias de la "resolución propuesta" ...

+0

hola, no leyó las reglas del estándar 8.5/16. Durante la inicialización de la copia, se utilizará la inicialización directa después de la conversión definida por el usuario, que, por supuesto, podrá usar el constructor de copia explícita. – user534498

+0

@ user534498: Bueno, parece que 12.3.1/2 requiere una sintaxis * de inicialización directa para que los constructores explícitos estén disponibles. Solo una mera inicialización directa no es suficiente, la * sintaxis * debe estar presente en el código. Y no está presente en el tuyo. – AnT

+0

Hola Andrey, por favor, lee mi edición 1. Por cierto, creo que el ejemplo en 12.3.1/2 no contradice mi razonamiento anterior porque no incluye un constructor de copia explícito. – user534498

0

Como mencionaste, gcc no compila el siguiente código.

struct A { 
    A(int) {} 
    explicit A(A const&) {} 
}; 

int main() { 
    A a = 2; 
} 

Creo que esto no es estándar conforme a la norma actual 8.5 p15.
Sin embargo, en cuanto a su primer caso,

struct A { 
    A(int) {} 
    explicit A(A const&) {} 
}; 

struct B { 
    operator A() { return 2; } 
}; 

int main() { 
    B b; 
    A a = b; 
} 

No estoy convencido de que esto es lo que permite conforme.
Como ya sabrá, return invocará el copiado dos veces conceptualmente.
return 2; construye un A implícitamente desde un int 2, y se usa para inicializar directamente un valor de retorno temporal (R).
Sin embargo, ¿debe aplicarse la inicialización directa a la segunda copia de R a a?
No pude encontrar la fraseología en el estándar actual que declara explícitamente que la inicialización directa se debe aplicar dos veces.
Como es seguro que esta secuencia daña la especificación explicit en un sentido , no me sorprende, incluso si los desarrolladores del compilador pensaran que permitir esto es un defecto.

Déjenme hacer una adición innecesaria.
El comportamiento no conforme de un compilador no significa que el compilador tenga un defecto directamente.
La norma ya tiene defectos a medida que aparecen los informes de defectos.
Por ejemplo, el estándar de C no permite la conversión de un puntero a una matriz de tipo T, a un puntero a una matriz de const T.
Esto está permitido en C++, y creo que se debe permitir semánticamente en C de manera similar.
Gcc emite una advertencia sobre esta conversión. Comeau emite un error y no compila.