sobrecargados sentencia modificadores ejemplos declarar crear constructores como clases clase amistad c++ c++11 gcc constructor

sentencia - public class en c++



Herencia de constructor e inicialización directa de miembro (2)

Esta pregunta ya tiene una respuesta aquí:

Intento utilizar una combinación de la inicialización de miembros de datos directos de C ++ 11 y la sintaxis de "usar" para heredar los constructores de una clase base. Ahora con gcc 5.4.0 (en Ubuntu 16.04) observo un error extraño, si el tipo de miembro de datos no tiene un constructor predeterminado. Probablemente sea más fácil de entender al mirar el siguiente ejemplo mínimo:

#include <iostream> struct Foo { Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; } }; struct Base { Base(int arg) { std::cout << "Base::Base(" << arg << ")" << std::endl; } }; struct Derived : public Base { using Base::Base; Foo foo{42}; }; int main() { Derived derived{120}; }

Este código se compila y ejecuta con el comportamiento esperado con clang. Con gcc no compila, porque el compilador elimina el constructor Derived::Derived(int) :

ttt.cpp: In function ‘int main()’: ttt.cpp:17:22: error: use of deleted function ‘Derived::Derived(int)’ Derived derived{120}; ^ ttt.cpp:12:15: note: ‘Derived::Derived(int)’ is implicitly deleted because the default definition would be ill-formed: using Base::Base; ^ ttt.cpp:12:15: error: no matching function for call to ‘Foo::Foo()’ ttt.cpp:4:3: note: candidate: Foo::Foo(int) Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; } ^ ttt.cpp:4:3: note: candidate expects 1 argument, 0 provided ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(const Foo&) struct Foo { ^ ttt.cpp:3:8: note: candidate expects 1 argument, 0 provided ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(Foo&&) ttt.cpp:3:8: note: candidate expects 1 argument, 0 provided

Si agrego un constructor predeterminado a Foo de esta manera:

Foo() { std::cout << "Foo::Foo()" << std::endl; };

también gcc puede compilarlo. El código se comporta exactamente de la misma manera, en particular, el constructor predeterminado de Foo nunca se ejecuta.

Entonces mi pregunta es ahora, ¿es este C ++ 11 válido? Si es así, probablemente haya encontrado un error en gcc. De lo contrario, ¿no deberían tanto gcc como clang darme un mensaje de error que indica que no es válido C ++ 11?

Editar después de que @ vlad-from-moscow haya respondido muy bien la pregunta: Este error parece estar presente también en gcc 6.2, así que presentaré un informe de error.

Segunda edición: Ya hay un error, que no encontré en la primera búsqueda: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054


El gcc no cumple con el estándar de C ++. El constructor heredado de la clase Derivado debe llamar al constructor Base en su lista de iniciadores de memoría con el argumento especificado para el constructor heredado Derivado.

Está escrito en el estándar C ++ (12.9 heredando constructor)

8 Un constructor heredero para una clase se define implícitamente cuando se usa odr (3.2) para crear un objeto de su tipo de clase (1.8). Un constructor de herencia definido implícitamente realiza el conjunto de inicializaciones de la clase que realizaría un constructor en línea escrito por el usuario para esa clase con una lista de iniciadores de memoria cuyo único inicializador de memoria tiene un identificador-inicializador de mem que nombra el clase base denotada en el especificador de nombre anidado de la declaración de uso y una lista de expresión como se especifica a continuación , y donde la sentencia compuesta en su cuerpo de función está vacía (12.6.2). Si el constructor escrito por el usuario estuviera mal formado, el programa está mal formado. Cada expresión en la lista de expresiones tiene la forma static_cast (p), donde p es el nombre del parámetro constructor correspondiente y T es el tipo declarado de p.

También según la sección (12.6.2 Inicializando bases y miembros)

8 En un constructor no delegante, si un miembro de datos no estático determinado o clase base no está designado por un identificador-inicializador-memoria (incluido el caso en el que no hay lista-inicializador-memoria porque el constructor tiene iniciador-noctor) y la entidad no es una clase base virtual de una clase abstracta (10.4), entonces

- si la entidad es un miembro de datos no estático que tiene un inicializador de llave o igual, la entidad se inicializa como se especifica en 8.5;


Parece que tienes razón, hay un error en gcc

De §12.9 [clase.inhctor]:

Una declaración-uso (7.3.3) que nombra un constructor declara implícitamente un conjunto de constructores heredadores . El conjunto candidato de constructores heredados de la clase X nombrada en la declaración de uso consiste en constructores reales y constructores nocionales que resultan de la transformación de los parámetros predeterminados de la siguiente manera:

  • todos los constructores que no sean plantillas de X

Así que esto significa que su clase Derived definitivamente debe obtener un constructor desde su base que acepte un int . Siguiendo las reglas normales de la inicialización de miembros en clase, construir una instancia de Derived no debería ser un problema sin un constructor predeterminado para Foo porque no se está utilizando. Por lo tanto, hay un error en gcc:

§13.3.1.3 Inicialización por constructor [over.match.ctor]

Cuando los objetos del tipo de clase son inicializados directamente (8.5) [...], la resolución de sobrecarga selecciona el constructor. Para la inicialización directa, las funciones candidatas son todos los constructores de la clase del objeto que se está inicializando .

Entonces el constructor Foo::Foo(int) debería haber sido seleccionado, lo que claramente no estaba en gcc.

Una pregunta que tuve después de leer esto fue "¿Esto hace que el constructor predeterminado para Derived se elimine?" La respuesta es no.

Convenientemente, el estándar proporciona un ejemplo debajo de este extracto (estoy suprimiendo lo que no se necesita):

struct B1 { B1(int); }; struct D1 : B1 { using B1::B1; };

El conjunto de constructores presentes en D1 es [ Énfasis mío]

  • D1() , constructor predeterminado declarado implícitamente, mal formado si odr-used
  • D1(const D1&) , constructor de copia declarado implícitamente, no heredado
  • D1(D1&&) , constructor de movimiento declarado implícitamente, no heredado
  • D1(int) , constructor de herencia declarado implícitamente