c++ - referencia - ¿Por qué el movimiento de clase derivada es construible cuando la clase base no es?
public y private en c++ (2)
Considere el siguiente ejemplo:
#include <iostream>
#include <string>
#include <utility>
template <typename Base> struct Foo : public Base {
using Base::Base;
};
struct Bar {
Bar(const Bar&) { }
Bar(Bar&&) = delete;
};
int main() {
std::cout << std::is_move_constructible<Bar>::value << std::endl; // NO
std::cout << std::is_move_constructible<Foo<Bar>>::value << std::endl; // YES. Why?!
}
¿Por qué el compilador genera un constructor de movimiento a pesar de que la clase base no es movible?
¿Está eso en el estándar o es un error del compilador? ¿Es posible "propagar perfectamente" la construcción del movimiento desde la base a la clase derivada?
Porque:
La resolución de sobrecarga ignora un constructor de movimiento predeterminado que se define como eliminado.
([class.copy] / 11)
El constructor de movimiento de
Bar
se
elimina explícitamente
, por lo que
Bar
no se puede mover.
Pero el constructor de movimiento de
Foo<Bar>
se
elimina implícitamente
después de ser declarado implícitamente como predeterminado, debido al hecho de que el miembro
Bar
no se puede mover.
Por lo tanto,
Foo<Bar>
puede moverse usando su constructor de copia.
Editar: También olvidé mencionar el hecho importante de que una declaración de constructor heredado, como el
using Base::Base
, no hereda los constructores predeterminados, de copia o de movimiento, por eso
Foo<Bar>
no tiene un constructor de movimiento eliminado explícitamente del
Bar
.
1. El comportamiento de
std::is_move_constructible
Este es el comportamiento esperado de std::is_move_constructible :
Los tipos sin un constructor de movimiento, pero con un constructor de copia que acepta argumentos
const T&
, satisfacenstd::is_move_constructible
.
Lo que significa que con un constructor de copia aún es posible construir
T
partir de la referencia de valor
T&&
.
Y
Foo<Bar>
tiene un
constructor de copia declarado implícitamente
.
2. El constructor de movimiento declarado implícitamente de
Foo<Bar>
¿Por qué el compilador genera un constructor de movimiento a pesar de que la clase base no es construible para mover?
De hecho, el constructor de movimiento de
Foo<Bar>
se define como
deleted
, pero tenga en cuenta que la resolución de sobrecarga ignora el constructor de movimiento declarado implícitamente eliminado.
El constructor de movimiento implícitamente declarado o predeterminado para la clase
T
se define como eliminado en cualquiera de los siguientes es verdadero:
... T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors); ...
La resolución de sobrecarga ignora el constructor de movimiento declarado implícitamente eliminado (de lo contrario, evitaría que la inicialización de copia rvalue).
3. El comportamiento diferente entre
Bar
y
Foo<Bar>
Tenga en cuenta que el constructor de movimiento de
Bar
se declara como
deleted
explícitamente, y el constructor de movimiento de
Foo<Bar>
se declara implícitamente y se define como
deleted
.
El punto es que
la resolución de sobrecarga ignora el constructor de movimiento declarado implícitamente eliminado
, lo que hace posible mover la construcción
Foo<Bar>
con su constructor de copia.
Pero el constructor de movimiento eliminado explícitamente participará en la resolución de sobrecarga, lo que significa que al intentar mover la
Bar
constructor se seleccionará el constructor de movimiento eliminado, entonces el programa está mal formado.
Es por eso que
Foo<Bar>
es un movimiento construible pero
Bar
no.
El estándar tiene una declaración explícita sobre esto. $ 12.8 / 11 Copiar y mover objetos de clase [class.copy]
La resolución de sobrecarga ignora un constructor de movimiento predeterminado que se define como eliminado ([over.match], [over.over]). [Nota: un constructor de movimiento eliminado interferiría con la inicialización de un valor r que puede usar el constructor de copia. - nota final]