Насколько полезным было бы наследование конструкторов в C++?
Когда я сижу на заседаниях Комитета по стандартам C++, они обсуждают плюсы и минусы удалениянаследующих конструкторов , поскольку ни один поставщик компилятора еще не реализовал его (смысл в том, что пользователи не просили об этом).
Позвольте мне быстро напомнить всем, что такое наследующие конструкторы:struct B
{
B(int);
};
struct D : B
{
using B::B;
};
Некоторые производители предлагают, что с R-значение ссылки и шаблоны с переменным числом аргументов (точная пересылка конструкторы), было бы тривиально, чтобы обеспечить переадресацию конструктор в наследующем классе, который бы исключал наследование конструкторов.
Например:
struct D : B
{
template<class ... Args>
D(Args&& ... args) : B(args...) { }
};
У меня есть два вопроса:
1) Можете ли вы привести реальные (не надуманные) примеры из вашего опыта программирования, которые значительно выиграли бы от наследования конструкторов?2) есть ли какие-либо технические причины, которые могут помешать "совершенным конструкторам пересылки" быть адекватной альтернативой?
Спасибо!
5 ответов:
2) есть ли какие-либо технические причины, которые вы можете придумать, которые помешали бы "совершенным конструкторам пересылки" быть адекватной альтернативой?Я показал здесь одну проблему с этим идеальным подходом к пересылке: пересылка всех конструкторов в C++0x.
Кроме того, идеальный метод пересылки не может "переадресовывать" эксплицитность конструкторов базового класса: либо это всегда конструктор преобразования, либо никогда, и базовый класс всегда будет быть прямым инициализированным (всегда использовать все конструкторы, даже явные).
Еще одна проблема-это конструкторы списка инициализаторов, потому что вы не можете вывести
Args
вinitializer_list<U>
. Вместо этого вам нужно будет перенаправить в базу с помощьюB{args...}
(Обратите внимание на фигурные скобки) и инициализировать объектыD
с помощью(a, b, c)
или{1, 2, 3}
или= {1, 2, 3}
. В этом случаеArgs
будет являться типами элементов списка инициализаторов и перенаправлять их в базовый класс. Конструктор списка инициализаторов может затем получить их. Это, кажется, вызвать ненужное раздувание кода, поскольку пакет аргументов шаблона потенциально будет содержать множество последовательностей типов для каждой различной комбинации типов и длины, а также поскольку вам нужно выбрать синтаксис инициализации, это означает: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] (!!)
Пара недостатков предлагаемого обходного пути:
В целом, когнитивная сложность обходного пути очень и очень плоха. Гораздо хуже, чем, например, дефолтные специальные функции-члены, для которых был добавлен простой синтаксис.
- он длиннее
- у него больше жетонов
- он использует совершенно новые сложные языковые функции
Реальная мотивация наследования конструктора: AOP mix-ins реализован с использованием повторного наследования вместо множественного наследование.
В дополнение к тому, что сказали другие, рассмотрим этот искусственный пример:
#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; }
По крайней мере, в MinGW g++ 4.4.1 компиляция завершается неудачей из-за переадресации конструктора C++0x.
Он прекрасно компилируется с" ручной " переадресацией (закомментированные конструкторы), и предположительно / возможно также с унаследованными конструкторами?
Ура & ХТ.
Я вижу проблему, когда новый класс имеет переменные-члены, которые должны быть инициализированы в конструкторе. Это будет обычным случаем, так как обычно производный класс добавляет некоторое состояние к базовому классу.
То есть:
struct B { B(int); }; struct D : B { D(int a, int b) : B(a), m(b) {} int m; };
Для тех, кто пытается ее решить: как вы отличаете
Если будет решен только самый простой случай, то он будет иметь очень ограниченную полезность на практике. Нет удивительно, что поставщики компиляторов еще не реализовали это предложение.:B(a), m(b)
от:B(b), m(a)
? Как вы справляетесь с множественным наследованием? виртуальное наследование?
С философской точки зрения я против наследования конструкторов. Если вы определяете новый класс, вы определяете, как он будет создан. Если большая часть этой конструкции может иметь место в базовом классе, то вполне разумно переслать эту работу конструктору базового класса в списке инициализации. Но вам все равно нужно явно это сделать.