2010-11-08 7 views
31

Mentre mi siedo alle riunioni del comitato per gli standard C++, stanno discutendo i pro e i contro del rilascio di Inheriting Constructors poiché nessun fornitore di compilatore l'ha ancora implementato (il senso è che gli utenti non lo hanno richiesto).Quanto sarebbe utile ereditare costruttori in C++?

Vorrei ricordare rapidamente a tutti quello costruttori Ereditare sono:

struct B 
{ 
    B(int); 
}; 

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

Alcuni fornitori propongono che, con riferimenti r-value e modelli variadic (costruttori di inoltro perfetto), sarebbe banale per fornire un costruttore di inoltro in la classe ereditaria che eliminerebbe i costruttori ereditari.

Per es .:

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

Ho due domande:

1) Potete fornire esempi reali (non inventati) dalla vostra esperienza di programmazione che potrebbero trarre beneficio in modo significativo da ereditare costruttori?

2) Esistono motivi tecnici a cui si può pensare che impediscano ai "costruttori di inoltro perfetti" di essere un'alternativa adeguata?

Grazie!

+1

Buon divertimento lì. Speriamo che elaborino tutti quei problemi cricici :) –

+1

@Johannes: Grazie! Alcuni DR sono stati discussi :) –

+0

un ovvio problema con i corrieri di inoltro perfetti potrebbe essere che non sono molto più brevi. Per molte classi, potrei semplicemente scrivere un costruttore di inoltro "tradizionale" con la stessa quantità di digitazione. – jalf

risposta

21

2) Esistono motivi tecnici a cui si può pensare che impediscano ai "costruttori di inoltro perfetti" di essere un'alternativa adeguata?

Ho mostrato un problema con questo perfetto approccio di inoltro qui: Forwarding all constructors in C++0x.

Inoltre, l'approccio di inoltro perfetto non può "inoltrare" l'esplicita dei costruttori di classe base: O è sempre un costruttore di conversione o mai, e la classe base sarà sempre inizializzata direttamente (facendo sempre uso di tutti costruttori, anche quelli espliciti).

Un altro problema sono i costruttori di inizializzatore-elenco perché non è possibile dedurre Args in initializer_list<U>. Invece, è necessario inoltrare alla base con B{args...} (notare le parentesi) e inizializzare gli oggetti D con (a, b, c) o {1, 2, 3} o = {1, 2, 3}. In tal caso, Args sarebbero i tipi di elementi dell'elenco di inizializzazione e li inoltreranno alla classe base. Un costruttore di inizializzatore-lista può quindi riceverli. Questo sembra causare il codice inutile gonfiare perché il pacchetto modello argomento sarà contenere potenzialmente un sacco di sequenze di tipo per ogni diversa combinazione di tipi e lunghezza e perché si deve scegliere una sintassi di inizializzazione questo 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] (!!) 
+1

molto bello! Grazie. –

-1

Filosoficamente, I' m contro i costruttori ereditari. Se stai definendo una nuova classe, stai definendo come verrà creata. Se la maggior parte di tale costruzione può aver luogo nella classe base, è del tutto ragionevole che tu possa inoltrare tale lavoro al costruttore della classe base nella lista di inizializzazione. Ma hai ancora bisogno di farlo esplicitamente.

+9

Immagino che non hai mai ereditato da un parametro template? –

+0

Questo indica solo una posizione senza spiegare perché la si tiene, e quindi non sembra aggiungere alcun valore. _Perché sei contrario ai costruttori ereditari? _Perché, filosoficamente, gli utenti devono inoltrare esplicitamente? –

4

Un paio di inconvenienti per la soluzione proposta:

  • E 'più
  • è avuto più gettoni
  • utilizza nuovissime caratteristiche del linguaggio complicate

Nel complesso, la complessità cognitiva del la soluzione è molto pessima. Molto peggio di ad es. funzioni membro speciali predefinite, per le quali è stata aggiunta una semplice sintassi.

Motivazione del mondo reale per l'ereditarietà del costruttore: i mix-in AOP implementati utilizzando l'ereditarietà ripetuta anziché l'ereditarietà multipla.

+0

Potrei aggiungere alla tua lista che potrebbe causare un sacco di brutti risultati del compilatore, come qualsiasi cosa che riguarda i template. – notlesh

0

Vedo un problema quando la nuova classe ha variabili membro che devono essere inizializzate nel costruttore. Questo sarà il caso comune, in quanto solitamente una classe derivata aggiungerà una sorta di stato alla classe base.

cioè:

struct B 
{ 
    B(int); 
}; 

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

Per coloro che cercano di risolverlo: come si fa a distinguere tra :B(a), m(b) e :B(b), m(a)? Come gestisci l'ereditarietà multipla? eredità virtuale?

Se solo il caso più semplice viene risolto, avrà un'utilità molto limitata nella pratica. Non c'è da meravigliarsi se i produttori di compilatori non hanno ancora implementato la proposta.

+0

Dipende da come funziona un contructor ereditato. Non ho controllato, ma presumo che sarebbe predefinito costruire la classe derivata. In tal caso, un costruttore predefinito di classe derivata farebbe il lavoro? ** EDIT **: No, quello era un thinko! Cheers, –

+0

@Alf In questo caso, l'utilizzo è limitato ai casi in cui la classe derivata può essere costruita come predefinita. Nessuno dei casi del mondo reale che ho incontrato in cui l'inoltro del costruttore sarebbe stato utile, sarebbe stato risolto da quello. – Sjoerd

+0

Ho incontrato molti casi in cui è utile. Pensa a classi derivate che implementano le funzioni virtuali in modo diverso, ma prendi lo stesso, probabilmente molto ampio, set di argomenti del costruttore. L'utilizzo di un costruttore ereditario è di gran lunga preferibile per l'inoltro manuale di tutti questi argomenti, _e_ per utilizzare l'inoltro del modello con i vari svantaggi che ne derivano (in gran parte legati alla deduzione/restringimento). –

3

In aggiunta a ciò che altri hanno detto, si consideri questo esempio artificiale:

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

Almeno con MinGW g ++ 4.4.1, la compilazione fallisce a causa della deviazione costruttore di C++ 0x.

Compila bene con l'inoltro "manuale" (costruttori commentati) e presumibilmente/probabilmente anche con costruttori ereditati?

Cheers & hth.