¿Qué tan útil sería Heredar Constructores en C++?


Mientras me siento en las reuniones del comité de Estándares de C++, están discutiendo los pros y los contras de eliminar Heredar Constructores ya que ningún proveedor de compiladores lo ha implementado todavía (el sentido es que los usuarios no lo han estado pidiendo).

Permítanme recordarles rápidamente a todos lo que son los constructores herederos:

struct B
{
   B(int);
};

struct D : B
{
  using B::B;
};

Algunos proveedores están proponiendo que con referencias de valor r y plantillas variádicas (constructores de reenvío perfecto), sería trivial proporcionar un reenvío constructor en la clase de herencia que obviaría a los constructores de herencia.

Por ejemplo:

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

Tengo dos preguntas:

1) ¿Puede proporcionar ejemplos del mundo real (no artificiales) de su experiencia de programación que se beneficiarían significativamente de heredar constructores?

2) ¿Hay alguna razón técnica que se le ocurra que impida que los "constructores de reenvío perfecto" sean una alternativa adecuada?

Gracias!

Author: curiousguy, 2010-11-09

5 answers

2) ¿Hay alguna razón técnica que se le ocurra que impida que los "constructores de reenvío perfecto" sean una alternativa adecuada?

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

Además, el enfoque de reenvío perfecto no puede "reenviar" la explicitud de los constructores de clase base: O siempre es un constructor de conversión o nunca, y la clase base siempre ser inicializado directamente (siempre haciendo uso de todos los constructores, incluso los explícitos).

Otro problema son los constructores de lista inicializadora porque no se puede deducir Args a initializer_list<U>. En su lugar, tendría que reenviar a la base con B{args...} (tenga en cuenta las llaves) e inicializar D objetos con (a, b, c) o {1, 2, 3} o = {1, 2, 3}. En ese caso, Args serían los tipos de elementos de la lista inicializador, y los reenviarían a la clase base. Un constructor inicializador-lista puede recibirlos. Esto parece causa un exceso de código innecesario porque el paquete de argumentos de plantilla potencialmente contendrá muchas secuencias de tipos para cada combinación diferente de tipos y longitud y porque 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] (!!)
 21
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:00:06

Un par de inconvenientes para la solución propuesta:

  • Es más largo
  • Tiene más fichas
  • Utiliza nuevas características del lenguaje complicado

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

Motivación en el mundo real para la herencia de constructores: Mix-ins AOP implementados usando herencia repetida en lugar de múltiple herencia.

 4
Author: Ben Voigt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-08 23:26:32

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?

Salud y hth.

 3
Author: Cheers and hth. - Alf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-09 00:28:08

Veo un problema cuando la nueva clase tiene variables miembro que necesitan ser inicializadas en el constructor. Este será el caso común, ya que generalmente 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 tratan de resolverlo: ¿cómo distinguir entre :B(a), m(b) y :B(b), m(a) ? ¿Cómo se maneja 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 me pregunto si los proveedores del compilador aún no han implementado la propuesta.

 0
Author: Sjoerd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-08 23:52:40

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

 -1
Author: miked,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-08 23:25:22