relaciones que programacion polimorfismo persona objeto herencia entre constructores composicion clases clase c++ inheritance constructor c++11 perfect-forwarding

que - ¿Qué tan útil sería heredar constructores en C++?



que es un objeto en c++ (5)

Mientras me siento en las reuniones del comité de estándares de C ++, están discutiendo los pros y los contras de dejar Inheriting Constructors ya que ningún proveedor de compiladores lo ha implementado (el sentido de que los usuarios no lo han estado pidiendo).

Permítanme recordarles a todos lo que son los constructores heredados:

struct B { B(int); }; struct D : B { using B::B; };

Algunos proveedores están proponiendo que con referencias r-value y plantillas variadic (constructores perfectos de reenvío), sería trivial proporcionar un constructor de reenvío en la clase heredada que obviaría la herencia de constructores.

Por ejemplo:

struct D : B { template<class ... Args> D(Args&& ... args) : B(args...) { } };

Tengo dos preguntas:

1) ¿Puede proporcionar ejemplos reales (no artificiales) de su experiencia de programación que se beneficiarían significativamente de la herencia de constructores?

2) ¿Hay alguna razón técnica que se pueda pensar que impediría que los "constructores de reenvíos perfectos" sean una alternativa adecuada?

¡Gracias!


2) ¿Hay alguna razón técnica que se pueda pensar que impediría que los "constructores de reenvíos perfectos" sean una alternativa adecuada?

He mostrado un problema con ese enfoque de reenvío perfecto aquí: Reenviar todos los constructores en C ++ 0x .

Además, el enfoque de reenvío perfecto no puede "reenviar" la expliciteness de los constructores de la clase base: o bien es siempre un constructor de conversión o nunca, y la clase base siempre se inicializará directamente (siempre haciendo uso de todos los constructores, incluso explícitos) unos)

Otro problema son los constructores de lista de inicializadores porque no puede deducir Args a initializer_list<U> . En su lugar, necesitaría reenviar a la base con B{args...} (observe las llaves) e inicialice los objetos D con (a, b, c) o {1, 2, 3} o = {1, 2, 3} . En ese caso, Args sería los tipos de elementos de la lista de inicializadores y los reenviaría a la clase base. Un constructor de lista de inicializadores puede entonces recibirlos. Esto parece causar un exceso de código innecesario porque el paquete de argumento de la plantilla potencialmente contendrá muchas secuencias de tipo para cada combinación diferente de tipos y longitud, y debido a que tiene que elegir una sintaxis de inicialización, esto significa:

struct MyList { // initializes by initializer list MyList(std::initializer_list<Data> list); // initializes with size copies of def MyList(std::size_t size, Data def = Data()); }; MyList m{3, 1}; // data: [3, 1] MyList m(3, 1); // data: [1, 1, 1] // either you use { args ... } and support initializer lists or // you use (args...) and won''t struct MyDerivedList : MyList { template<class ... Args> MyDerivedList(Args&& ... args) : MyList{ args... } { } }; MyDerivedList m{3, 1}; // data: [3, 1] MyDerivedList m(3, 1); // data: [3, 1] (!!)


Además de lo que otros han dicho, considere este ejemplo artificial:

#include <iostream> class MyString { public: MyString( char const* ) {} static char const* name() { return "MyString"; } }; class MyNumber { public: MyNumber( double ) {} static char const* name() { return "MyNumber"; } }; class MyStringX: public MyString { public: //MyStringX( char const* s ): MyString( s ) {} // OK template< class ... Args > MyStringX( Args&& ... args ): MyString( args... ) {} // !Nope. static char const* name() { return "MyStringX"; } }; class MyNumberX: public MyNumber { public: //MyNumberX( double v ): MyNumber( v ) {} // OK template< class ... Args > MyNumberX( Args&& ... args ): MyNumber( args... ) {} // !Nope. static char const* name() { return "MyNumberX"; } }; typedef char YesType; struct NoType { char x[2]; }; template< int size, class A, class B > struct Choose_{ typedef A T; }; template< class A, class B > struct Choose_< sizeof( NoType ), A, B > { typedef B T; }; template< class Type > class MyWrapper { private: static Type const& dummy(); static YesType accept( MyStringX ); static NoType accept( MyNumberX ); public: typedef typename Choose_< sizeof( accept( dummy() ) ), MyStringX, MyNumberX >::T T; }; int main() { using namespace std; cout << MyWrapper< int >::T::name() << endl; cout << MyWrapper< char const* >::T::name() << endl; }

Al menos con MinGW g ++ 4.4.1, la compilación falla debido al reenvío del constructor C ++ 0x.

Se compila bien con el reenvío "manual" (constructores comentados), y presumiblemente / posiblemente también con constructores heredados?

Saludos y hth.


Filosóficamente, estoy en contra de heredar constructores. Si está definiendo una nueva clase, está definiendo cómo se creará. Si la mayoría de esa construcción puede tener lugar en la clase base, entonces es totalmente razonable que reenvíes ese trabajo al constructor de la clase base en la lista de inicialización. Pero aún debes hacerlo explícitamente.


Un par de inconvenientes en la solución propuesta:

  • Es mas largo
  • Tiene más tokens
  • Utiliza nuevas funciones de lenguaje complicado

En general, la complejidad cognitiva de la solución alternativa es muy mala. Mucho peor que, por ejemplo, las funciones de miembros especiales en default, para las cuales se agregó una sintaxis simple.

Motivo del mundo real para herencia de constructores: mezclas AOP implementadas usando herencia repetida en lugar de herencia múltiple.


Veo un problema cuando la nueva clase tiene variables miembro que deben inicializarse en el constructor. Este será el caso común, ya que normalmente una clase derivada agregará algún tipo de estado a la clase base.

Es decir:

struct B { B(int); }; struct D : B { D(int a, int b) : B(a), m(b) {} int m; };

Para aquellos que intentan resolverlo: ¿cómo se distingue entre :B(a), m(b) y :B(b), m(a) ? ¿Cómo manejas la herencia múltiple? herencia virtual?

Si solo se resuelve el caso más simple, tendrá una utilidad muy limitada en la práctica. No es de extrañar que los proveedores de compiladores aún no hayan implementado la propuesta.