31 votes

Dans quelle mesure les constructeurs hérités seraient-ils utiles en C ++?

Comme je suis assis dans le C++ réunions des comités de normalisation, ils sont en train de discuter les avantages et les inconvénients de l'abandon de la Hériter des Constructeurs car aucun fournisseur de compilateur a encore mise en oeuvre (le sens étant les utilisateurs n'ont pas été de le demander).

Permettez-moi de rappeler à tout le monde ce hériter constructeurs sont:

struct B
{
   B(int);
};

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

Certains fournisseurs proposent que le r-valeur de références et les variadic templates (transfert parfait des constructeurs), il serait trivial de fournir un renvoi d'un constructeur dans la classe qui hérite qui écarterait hériter des constructeurs.

Par exemple:

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

J'ai deux questions:

1) Pouvez-vous fournir monde réel (non-fictive) des exemples de votre expérience de la programmation qui permettrait de bénéficier de manière significative d'hériter des constructeurs?

2) existe-il des raisons techniques que vous pouvez penser qui l'empêcherait de "transfert parfait des constructeurs" d'être une bonne alternative?

Merci!

21voto

Johannes Schaub - litb Points 256113

2) existe-il des raisons techniques que vous pouvez penser qui l'empêcherait de "transfert parfait des constructeurs" d'être une bonne alternative?

Je l'ai montré à un problème de transfert parfait approche ici: Renvoi de tous les constructeurs dans C++0x .

Aussi, le transfert parfait approche ne peut pas "en avant" de la expliciteness de la classe de base des constructeurs: Soit c'est toujours une conversion constructeur ou jamais, et la classe de base sont toujours initialisés (toujours en faisant usage de tous les constructeurs, même explicite ceux).

Un autre problème sont initialiseur-liste des constructeurs parce que vous ne pouvez pas déduire Args de initializer_list<U>. Au lieu de cela, vous devez le transmettre à la base avec B{args...} (note les accolades) et initialiser D objets avec (a, b, c) ou {1, 2, 3} ou = {1, 2, 3}. Dans ce cas, Args serait le type d'élément de la liste d'initialiseur, et de les transmettre à la classe de base. Un initialiseur-liste constructeur peut alors recevoir. Ce qui semble susciter de l'augmentation du code parce que l'argument de modèle pack seront susceptibles de contenir beaucoup de séquences de type différent pour chaque combinaison de types et longueur et que vous devez choisir une syntaxe d'initialisation cela signifie:

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] (!!)

4voto

Ben Voigt Points 151460

Quelques inconvénients à la solution de contournement proposée:

  • C'est plus long
  • Il a plus de jetons
  • Il utilise de nouvelles fonctionnalités de langage compliquées

Dans l'ensemble, la complexité cognitive de la solution de contournement est très très mauvaise. Bien pire que par exemple les fonctions membres spéciales par défaut, pour lesquelles une syntaxe simple a été ajoutée.

Motivation du monde réel pour l'héritage de constructeur: les mélanges AOP sont mis en œuvre en utilisant l'héritage répété au lieu de l'héritage multiple.

3voto

En plus de ce que d'autres ont dit, considérez cet exemple artificiel:

 #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;
}
 

Au moins avec MinGW g ++ 4.4.1, la compilation échoue en raison du transfert du constructeur C ++ 0x.

Il compile bien avec le transfert "manuel" (constructeurs commentés), et probablement / éventuellement aussi avec les constructeurs hérités?

Santé et hth.

0voto

Sjoerd Points 4548

Je vois un problème lors de la nouvelle classe a de membres des variables qui doivent être initialisés dans le constructeur. Ce sera le cas le plus courant, comme d'habitude une classe dérivée va ajouter une sorte d'état de la classe de base.

C'est:

struct B 
{ 
   B(int); 
}; 

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

Pour ceux qui essaient de le résoudre: comment faites-vous la distinction entre :B(a), m(b) et :B(b), m(a) ? Comment gérez-vous l'héritage multiple? l'héritage virtuel?

Si seulement le cas le plus simple est résolu, il y aura très peu utile dans la pratique. Pas étonnant que les éditeurs de compilateurs n'ont pas mis en œuvre la proposition encore.

-1voto

miked Points 1993

Philosophiquement, je suis contre l'héritage des constructeurs. Si vous définissez une nouvelle classe, vous définissez comment elle va être créée. Si la majeure partie de cette construction peut avoir lieu dans la classe de base, il est tout à fait raisonnable pour vous de transmettre ce travail au constructeur de la classe de base dans la liste d'initialisation. Mais vous devez toujours le faire explicitement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X