Насколько полезным было бы наследование конструкторов в 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 31

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)? Как вы справляетесь с множественным наследованием? виртуальное наследование?

Если будет решен только самый простой случай, то он будет иметь очень ограниченную полезность на практике. Нет удивительно, что поставщики компиляторов еще не реализовали это предложение.

С философской точки зрения я против наследования конструкторов. Если вы определяете новый класс, вы определяете, как он будет создан. Если большая часть этой конструкции может иметь место в базовом классе, то вполне разумно переслать эту работу конструктору базового класса в списке инициализации. Но вам все равно нужно явно это сделать.