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

В дополнение к тому, что сказали другие, рассмотрим этот искусственный пример:

#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.

Он прекрасно компилируется с "ручной" пересылкой (закомментированными конструкторами), и предположительно / возможно также с унаследованными конструкторами?

Ура & hth.

Я вижу проблему, когда новый класс имеет переменные-члены, которые должны быть инициализированы в конструкторе. Это будет распространенным случаем, так как обычно производный класс добавляет какое-то состояние в базовый класс.

То есть:

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

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

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

Другие вопросы по тегам