c++ - resueltos - miembros de una clase en programacion orientada a objetos
¿Por qué una inicialización de miembro in situ usa un constructor de copia en C++ 11? (2)
Consideremos el primer caso.
struct A {
std::atomic<int> a = 0;
};
Para que esta inicialización tenga éxito, es necesario que haya un constructor de copia accesible. Pero el constructor de copia se define como eliminado.
atomic(const atomic&) = delete;
Entonces el compilador emite un error.
En el segundo caso
struct A {
std::atomic<int> a = {0};
};
donde se usa una lista de inicializadores, no se requiere el constructor de copia. El compilador busca un constructor que acepte un argumento int
y tal constructor existe, por lo que se llama.
constexpr atomic(T) noexcept;
o si sustituir el parámetro de plantilla por el tipo int
constexpr atomic(int) noexcept;
De acuerdo con el estándar de C ++, si una clase no tiene constructor con el primer parámetro de tipo std :: initializer_list (cuando se especifica una lista de inicializador), entonces
3 La inicialización en lista de un objeto o referencia de tipo T se define de la siguiente manera: ...
De lo contrario, si T es un tipo de clase, los constructores son considerados. Los constructores correspondientes se enumeran y el mejor se elige mediante resolución de sobrecarga (13.3, 13.3.1.7). Si se requiere una conversión de reducción (ver más abajo) para convertir cualquiera de los argumentos, el programa no está bien formado.
En el ultimo caso
A a;
a.a = 1;
esto utiliza un operador de asignación
T operator=(T) noexcept;
o si sustituir el parámetro de plantilla por el tipo int
int operator=(int) noexcept;
Así que no hay problema.
Estoy un poco confundido sobre el siguiente código:
struct A {
std::atomic<int> a = 0;
};
Lo que da un error:
El subobjeto miembro de copia de tipo ''std :: atomic'' invoca el constructor eliminado
Pero casi el mismo código funciona:
struct A {
std::atomic<int> a = {0};
};
Okey, si la primera variante requiere el constructor de copia, entonces tiene que usar operator=()
. ¡Pero espera! Este operador funciona perfectamente sin el constructor de copia:
A a;
a.a = 1;
¿Puede alguien explicar cómo se expanden ambas inicializaciones en el lugar en términos de operaciones simples? ¿Por qué el primero requiere copia constructor?
Todas las referencias son a N3797, el borrador de trabajo actual de C ++ 1y. §8.5 Inicializadores [dcl.init] / 15 estados:
La inicialización que se produce en el formulario.
T x = a;
así como en el paso de argumentos, el retorno de función, el lanzamiento de una excepción (15.1), el manejo de una excepción (15.3) y la inicialización agregada de miembros (8.5.1) se denomina inicialización de copia . [Nota: la inicialización de la copia puede invocar un movimiento (12.8). "Nota final"
Así que la declaración:
std::atomic<int> a = 0;
está realizando copia-inicialización. Según 8.5 / 17:
La semántica de los inicializadores es la siguiente. El tipo de destino es el tipo de objeto o referencia que se está inicializando y el tipo de origen es el tipo de la expresión de inicializador. Si el inicializador no es una expresión única (posiblemente entre paréntesis), el tipo de fuente no está definido.
El tipo de destino aquí es std::atomic<int>
y el tipo de origen es int
(es decir, decltype(0)
). Para determinar la semántica de la inicialización, debemos determinar cuál de las viñetas del párrafo 17 se aplica:
- Si el inicializador es una lista-iniciada con paréntesis (no entre paréntesis), el objeto o la referencia se inicializa en la lista (8.5.4).
- Si el tipo de destino es un tipo de referencia, consulte 8.5.3.
- Si el tipo de destino es una matriz de caracteres, una matriz de
char16_t
, una matriz dechar32_t
o una matriz dewchar_t
, y el inicializador es un literal de cadena, consulte 8.5.2.- Si el inicializador es
()
, el objeto está inicializado por valores.- De lo contrario, si el tipo de destino es una matriz, el programa está mal formado.
- Si el tipo de destino es un tipo de clase (posiblemente cv-calificado):
- Si la inicialización es una inicialización directa, o si es una inicialización de copia donde la versión no calificada de CV del tipo de origen es la misma clase que la clase del destino, o una clase derivada de la misma, ... [no se aplica , el tipo de fuente es
int
]- De lo contrario (es decir, para los casos restantes de inicialización de copia), las secuencias de conversión definidas por el usuario que pueden convertirse del tipo de origen al tipo de destino o (cuando se usa una función de conversión) a una clase derivada del mismo se enumeran como se describe en 13.3. 1.4, y la mejor se elige mediante resolución de sobrecarga (13.3). Si la conversión no se puede realizar o es ambigua, la inicialización no se ha realizado correctamente. La función seleccionada se llama con la expresión inicializadora como su argumento; si la función es un constructor, la llamada inicializa un temporal de la versión no calificada cv del tipo de destino. El temporal es un prvalue. El resultado de la llamada (que es el temporal para el caso del constructor) se utiliza para inicializar directamente, de acuerdo con las reglas anteriores, el objeto que es el destino de la inicialización de la copia. En ciertos casos, se permite una implementación para eliminar la copia inherente a esta inicialización directa mediante la construcción del resultado intermedio directamente en el objeto que se está inicializando; ver 12.2, 12.8.
- ...
Ahí estamos. La expresión de inicialización - 0
- se convierte en std::atomic<int>
mediante la creación de un objeto temporal inicializado con el constructor std::atomic<int>(int)
. Ese objeto temporal se usa para inicializar directamente el objeto std::atomic<int>
. La otra de las viñetas de "clase de clase (posiblemente calificada para cv)" que ignoramos antes ahora se aplica:
- Si la inicialización es una inicialización directa, o si es una inicialización de copia donde la versión no calificada cv del tipo de origen es la misma clase que la clase de destino, o una clase derivada de la clase de destino, se consideran constructores. Los constructores correspondientes se enumeran (13.3.1.3) y el mejor se elige mediante resolución de sobrecarga (13.3). El constructor así seleccionado se llama para inicializar el objeto, con la expresión inicializadora o lista de expresiones como su argumento (s). Si no se aplica ningún constructor, o la resolución de sobrecarga es ambigua, la inicialización está mal formada.
Recuerde que el nuevo inicializador es un prvalue std::atomic<int>
. La resolución de sobrecarga determina que no hay un constructor std::atomic<int>
apropiado que acepte un solo argumento std::atomic<int>&&
( std::atomic<int>
no es movible ni puede copiarse) y diagnostica el programa como incorrecto formado.
Para la segunda parte de la pregunta,
std::atomic<int> a = {0};
Es nuevamente copia de inicialización por 8.5 / 15. Esta vez, sin embargo, se aplica la primera viñeta de 8.5 / 17:
- Si el inicializador es una lista-iniciada con paréntesis (no entre paréntesis), el objeto o la referencia se inicializa en la lista (8.5.4).
Para la inicialización de listas , debemos mirar a 8.5.4 / 3:
La inicialización de lista de un objeto o referencia de tipo
T
se define de la siguiente manera:
- Si
T
es un agregado, se realiza la inicialización agregada (8.5.1).- De lo contrario, si la lista de inicializadores no tiene elementos y
T
es un tipo de clase con un constructor predeterminado, el objeto se inicializa con valores.- De lo contrario, si
T
es una especialización destd::initializer_list<E>
, un objeto prvalueinitializer_list
se construye como se describe a continuación y se usa para inicializar el objeto de acuerdo con las reglas de inicialización de un objeto de una clase del mismo tipo (8.5) .- De lo contrario, si
T
es un tipo de clase, los constructores son considerados. Los constructores correspondientes se enumeran y el mejor se elige mediante resolución de sobrecarga (13.3, 13.3.1.7). Si se requiere una conversión de reducción (ver más abajo) para convertir cualquiera de los argumentos, el programa no está bien formado.- ...
std::atomic<int>
es un tipo de clase, no una especialización agregada o initializer_list
, por lo que se consideran constructores. El constructor std::atomic<int>::atomic(int)
se seleccionará como una coincidencia perfecta y se utiliza para inicializar el objeto.