Насколько полезным будет наследование конструкторов в C ++? - PullRequest
31 голосов
/ 09 ноября 2010

Пока я сижу на заседаниях комитета по стандартам 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 ]

21 голосов
/ 09 ноября 2010

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] (!!)
4 голосов
/ 09 ноября 2010

Пара недостатков предлагаемого обходного пути:

  • Это длиннее
  • У него больше токенов
  • Используются новые сложные языковые функции

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

Реальная мотивация для наследования конструктора: дополнения AOP реализованы с использованием многократного наследования вместо множественного наследования.

3 голосов
/ 09 ноября 2010

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

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

0 голосов
/ 09 ноября 2010

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

То есть:

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

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

0 голосов
/ 09 ноября 2010

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

...