【问题标题】:How useful would Inheriting Constructors be in C++?继承构造函数在 C++ 中有多大用处?
【发布时间】:2011-05-06 22:53:45
【问题描述】:

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

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

struct B
{
   B(int);
};

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

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

例如:

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

我有两个问题:

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

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

谢谢!

【问题讨论】:

  • 在那里玩得开心。让我们希望他们处理所有这些关键问题:)
  • @Johannes:谢谢!已经讨论了您的许多 DR :)
  • 完美转发ctor的一个明显问题可能是它们实际上并没有更短。对于很多类,我可以写一个“传统的”转发构造函数,输入相同的数量。
  • @jalf:我想动机是干掉参数列表的复制。而且我认为出于这个原因添加一个全新的语言功能可能只是一点点强迫症。

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


【解决方案1】:

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

我在这里展示了这种完美转发方法的一个问题:Forwarding all constructors in C++0x

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

另一个问题是初始化列表构造函数,因为您不能将Args 推导出为initializer_list&lt;U&gt;。相反,您需要使用B{args...}(注意大括号)转发到基址,并使用(a, b, c){1, 2, 3}= {1, 2, 3} 初始化D 对象。在这种情况下,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] (!!)

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

    • 我可能会在您的列表中添加它可能会导致很多丑陋的编译器输出,就像任何涉及模板的东西一样。
    【解决方案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。

    【讨论】:

      【解决方案4】:

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

      即:

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

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

      【讨论】:

      • 取决于继承的构造函数的工作方式。我没有检查,但假设它会默认构造派生类。在那种情况下,派生类默认构造函数可以完成这项工作吗? 编辑:不,那是一个想法!干杯,
      • @Alf 在这种情况下,使用仅限于可以默认构造派生类的情况。我遇到的任何构造函数转发有用的真实案例都不会因此得到解决。
      • 我遇到过很多有用的案例。考虑以不同方式实现虚函数的派生类,但采用相同的 - 可能非常大的 - 构造函数参数集。使用继承构造函数比手动转发所有这些参数要好得多,使用模板转发会带来各种缺点(主要与推导/缩小有关)。
      【解决方案5】:

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

      【讨论】:

      • 我猜你从来没有从模板参数继承?
      • 这只是陈述了一个立场而没有解释你为什么持有它,因此似乎没有增加任何价值。 为什么你反对继承构造函数? 为什么从哲学上讲,用户必须明确转发?
      猜你喜欢
      • 1970-01-01
      • 2015-01-17
      • 2015-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-09
      • 2019-01-21
      相关资源
      最近更新 更多