继承构造函数在C ++中有用吗?

时间:2010-11-08 23:09:51

标签: c++ inheritance constructor c++11 perfect-forwarding

当我参加C ++标准委员会会议时,他们正在讨论删除Inheriting Constructors的优缺点,因为还没有编译器供应商实现它(感觉是用户没有要求它)。 / p>

让我快速提醒大家继承构造函数是什么:

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 个答案:

答案 0 :(得分:21)

  

2)您是否有任何技术原因可以阻止“完美转发建设者”成为一个适当的替代方案?

我在这里展示了一个完美转发方法的问题:Forwarding all constructors in 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] (!!)

答案 1 :(得分:4)

建议的解决方法存在一些缺点:

  • 时间更长
  • 它有更多的令牌
  • 它使用全新的复杂语言功能

总体而言,变通方法的认知复杂性非常糟糕。比例如差很多默认的特殊成员函数,为其添加了简单的语法。

构造函数继承的真实动机:使用重复继承而不是多重继承实现AOP混合。

答案 2 :(得分:3)

除了其他人所说的,请考虑这个人为的例子:

#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构造函数转发,编译失败。

它通过“手动”转发(已注释掉的构造函数)进行编译,并且可能/也可能还有继承的构造函数?

干杯&amp;第h

答案 3 :(得分:0)

当新类具有需要在构造函数中初始化的成员变量时,我发现了一个问题。这将是常见的情况,因为通常派生类会向基类添加某种状态。

那是:

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)?你如何处理多重继承?虚拟继承?

如果只解决了最简单的案例,那么它在实践中的用处非常有限。难怪编译器供应商尚未实施该提案。

答案 4 :(得分:-1)

哲学上,我反对继承构造函数。如果您要定义一个新类,那么您将定义它将如何创建。如果大部分构造都可以在基类中进行,那么将该工作转发到初始化列表中的基类构造函数是完全合理的。但是你仍然需要明确地去做。