31

当我参加 C++ 标准委员会会议时,他们正在讨论放弃继承构造函数的利弊,因为还没有编译器供应商实现它(感觉用户并没有要求它)。

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

struct B
{
   B(int);
};

struct D : B
{
  using B::B;
};

一些供应商建议使用右值引用和可变参数模板(完美的转发构造函数),在继承类中提供转发构造函数来避免继承构造函数是微不足道的。

例如:

struct D : B
{
  template<class ... Args> 
    D(Args&& ... args) : B(args...) { } 
};

我有两个问题:

1)您能否从您的编程经验中提供真实世界(非人为)的示例,这些示例将从继承构造函数中受益匪浅?

2)您能想到哪些技术原因会阻止“完美的转发构造函数”成为适当的替代方案?

谢谢!

4

5 回答 5

21

2)您能想到哪些技术原因会阻止“完美的转发构造函数”成为适当的替代方案?

我在这里展示了这种完美转发方法的一个问题:在 C++0x 中转发所有构造函数

此外,完美的转发方法不能“转发”基类构造函数的显式性:要么它始终是转换构造函数,要么永远不会,并且基类将始终被直接初始化(始终使用所有构造函数,甚至显式那些)。

另一个问题是初始化列表构造函数,因为您无法Args推断initializer_list<U>. 相反,您需要使用B{args...}(注意大括号)转发到基础并使用or或初始化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] (!!)
于 2010-11-08T23:19:30.430 回答
4

建议的解决方法有几个缺点:

  • 它更长
  • 它有更多的代币
  • 它使用全新的复杂语言功能

总的来说,变通方法的认知复杂性非常非常糟糕。比添加了简单语法的默认特殊成员函数差得多。

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

于 2010-11-08T23:26:32.303 回答
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 构造函数转发,编译失败。

它可以通过“手动”转发(注释掉的构造函数)编译得很好,并且可能/可能也可以使用继承的构造函数?

干杯&hth。

于 2010-11-09T00:28:08.103 回答
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)?你如何处理多重继承?虚拟继承?

如果只解决最简单的情况,它在实践中的用处将非常有限。难怪编译器供应商还没有实施该提案。

于 2010-11-08T23:52:40.540 回答
-1

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

于 2010-11-08T23:25:22.767 回答