在c++中继承构造函数有多有用?

时间:2022-09-02 10:09:56

As I sit in the C++ Standards committee meetings, they are discussing the pros and cons of dropping Inheriting Constructors since no compiler vendor has implemented it yet (the sense being users haven't been asking for it).

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

Let me quickly remind everyone what inheriting constructors are:

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

struct B
{
   B(int);
};

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

Some vendors are proposing that with r-value references and variadic templates (perfect forwarding constructors), it would be trivial to provide a forwarding constructor in the inheriting class that would obviate inheriting constructors.

一些供应商建议使用r-value引用和变量模板(完美的转发构造函数),在继承类中提供一个可以避免继承构造函数的转发构造函数是很简单的。

For e.g.:

例如:

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

I have two questions:

我有两个问题:

1) Can you provide real world (non-contrived) examples from your programming experience that would benefit significantly from inheriting constructors?

1)能否从您的编程经验中提供一些真实的(非人为的)示例,以便从继承构造函数中获得显著的好处?

2) Are there any technical reasons you can think of that would preclude "perfect forwarding constructors" from being an adequate alternative?

2)你认为有什么技术原因可以阻止“完美的转发构造函数”成为一个合适的替代选择吗?

Thanks!

谢谢!

5 个解决方案

#1


21  

2) Are there any technical reasons you can think of that would preclude "perfect forwarding constructors" from being an adequate alternative?

2)你认为有什么技术原因可以阻止“完美的转发构造函数”成为一个合适的替代选择吗?

I have shown one problem with that perfect forwarding approach here: Forwarding all constructors in C++0x .

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

Also, the perfect forwarding approach can't "forward" the expliciteness of base-class constructors: Either it is always a converting constructor or never, and the base-class will always be direct initialized (always making use of all constructors, even explicit ones).

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

Another problem are initializer-list constructors because you can't deduce Args to initializer_list<U>. Instead, you would need to forward to the base with B{args...} (note the braces) and initialize D objects with (a, b, c) or {1, 2, 3} or = {1, 2, 3}. In that case, Args would be the element types of the initializer list, and forward them to the base class. A initializer-list constructor can then receive them. This seems to cause unnecessary code bloat because the template argument pack will potentially contain lots of type sequences for each different combination of types and length and because you have to choose an initialization syntax this means:

另一个问题是initializer-list构造函数,因为您不能将Args推到initializer_list。相反,您需要用B{args…}(注意括号)并使用(a、b、c)或{1、2、3}或={1、2、3}初始化D对象。在这种情况下,Args将是初始化器列表的元素类型,并将它们转发给基类。然后,initializer-list构造函数可以接收它们。这似乎会导致不必要的代码膨胀,因为模板参数包可能包含许多类型和长度不同组合的类型序列,因为您必须选择初始化语法,这意味着:

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


4  

A couple drawbacks to the proposed workaround:

提议的解决方案有几个缺点:

  • It's longer
  • 它是长的
  • It's got more tokens
  • 它有更多的令牌
  • It uses brand new complicated language features
  • 它使用了全新的复杂语言特性

Overall, the cognitive complexity of the workaround is very very bad. Much worse than e.g. defaulted special member functions, for which a simple syntax was added.

总的来说,解决方案的认知复杂性非常糟糕。比例如,默认的特殊成员函数要糟糕得多,其中添加了简单的语法。

Real-world motivation for constructor inheritance: AOP mix-ins implemented using repeated inheritance instead of multiple inheritance.

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

#3


3  

In addition to what others have said, consider this artifical example:

除了别人所说的,考虑一下这个人为的例子:

#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;
}

At least with MinGW g++ 4.4.1, compilation fails due to the C++0x constructor forwarding.

至少在MinGW g++ 4.4.1中,编译失败是由于c++ 0x构造函数的转发。

It compiles fine with the "manual" forwarding (commented out constructors), and presumably/possibly also with inherited constructors?

它可以与“手动”转发(注释出的构造函数)一起编译,也可以与继承的构造函数一起编译?

Cheers & hth.

& hth欢呼声。

#4


0  

I see a problem when the new class has member variables that need to be initialized in the constructor. This will be the common case, as usually a derived class will add some sort of state to the base class.

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

That is:

那就是:

struct B 
{ 
   B(int); 
}; 

struct D : B 
{ 
   D(int a, int b) : B(a), m(b) {}
   int m;
}; 

For those trying to solve it: how do you distinguish between :B(a), m(b) and :B(b), m(a) ? How do you handle multiple inheritance? virtual inheritance?

对于那些试图解决它的人:你如何区分:B(a), m(B)和:B(B), m(a) ?如何处理多重继承?虚拟继承?

If only the most simple case is solved, it will have very limited usefulness in practice. No wonder the compiler vendors haven't implemented the proposal yet.

如果仅仅解决了最简单的问题,那么它在实践中的作用就会非常有限。难怪编译器供应商还没有执行这个建议。

#5


-1  

Philosophically, I'm against inheriting constructors. If you're defining a new class, you're defining how it's going to be created. If most of that construction can take place in the base class, then it's totally reasonable for you to forward that work to the base class' constructor in the initialization list. But you still need to explicitly do it.

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

#1


21  

2) Are there any technical reasons you can think of that would preclude "perfect forwarding constructors" from being an adequate alternative?

2)你认为有什么技术原因可以阻止“完美的转发构造函数”成为一个合适的替代选择吗?

I have shown one problem with that perfect forwarding approach here: Forwarding all constructors in C++0x .

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

Also, the perfect forwarding approach can't "forward" the expliciteness of base-class constructors: Either it is always a converting constructor or never, and the base-class will always be direct initialized (always making use of all constructors, even explicit ones).

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

Another problem are initializer-list constructors because you can't deduce Args to initializer_list<U>. Instead, you would need to forward to the base with B{args...} (note the braces) and initialize D objects with (a, b, c) or {1, 2, 3} or = {1, 2, 3}. In that case, Args would be the element types of the initializer list, and forward them to the base class. A initializer-list constructor can then receive them. This seems to cause unnecessary code bloat because the template argument pack will potentially contain lots of type sequences for each different combination of types and length and because you have to choose an initialization syntax this means:

另一个问题是initializer-list构造函数,因为您不能将Args推到initializer_list。相反,您需要用B{args…}(注意括号)并使用(a、b、c)或{1、2、3}或={1、2、3}初始化D对象。在这种情况下,Args将是初始化器列表的元素类型,并将它们转发给基类。然后,initializer-list构造函数可以接收它们。这似乎会导致不必要的代码膨胀,因为模板参数包可能包含许多类型和长度不同组合的类型序列,因为您必须选择初始化语法,这意味着:

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


4  

A couple drawbacks to the proposed workaround:

提议的解决方案有几个缺点:

  • It's longer
  • 它是长的
  • It's got more tokens
  • 它有更多的令牌
  • It uses brand new complicated language features
  • 它使用了全新的复杂语言特性

Overall, the cognitive complexity of the workaround is very very bad. Much worse than e.g. defaulted special member functions, for which a simple syntax was added.

总的来说,解决方案的认知复杂性非常糟糕。比例如,默认的特殊成员函数要糟糕得多,其中添加了简单的语法。

Real-world motivation for constructor inheritance: AOP mix-ins implemented using repeated inheritance instead of multiple inheritance.

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

#3


3  

In addition to what others have said, consider this artifical example:

除了别人所说的,考虑一下这个人为的例子:

#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;
}

At least with MinGW g++ 4.4.1, compilation fails due to the C++0x constructor forwarding.

至少在MinGW g++ 4.4.1中,编译失败是由于c++ 0x构造函数的转发。

It compiles fine with the "manual" forwarding (commented out constructors), and presumably/possibly also with inherited constructors?

它可以与“手动”转发(注释出的构造函数)一起编译,也可以与继承的构造函数一起编译?

Cheers & hth.

& hth欢呼声。

#4


0  

I see a problem when the new class has member variables that need to be initialized in the constructor. This will be the common case, as usually a derived class will add some sort of state to the base class.

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

That is:

那就是:

struct B 
{ 
   B(int); 
}; 

struct D : B 
{ 
   D(int a, int b) : B(a), m(b) {}
   int m;
}; 

For those trying to solve it: how do you distinguish between :B(a), m(b) and :B(b), m(a) ? How do you handle multiple inheritance? virtual inheritance?

对于那些试图解决它的人:你如何区分:B(a), m(B)和:B(B), m(a) ?如何处理多重继承?虚拟继承?

If only the most simple case is solved, it will have very limited usefulness in practice. No wonder the compiler vendors haven't implemented the proposal yet.

如果仅仅解决了最简单的问题,那么它在实践中的作用就会非常有限。难怪编译器供应商还没有执行这个建议。

#5


-1  

Philosophically, I'm against inheriting constructors. If you're defining a new class, you're defining how it's going to be created. If most of that construction can take place in the base class, then it's totally reasonable for you to forward that work to the base class' constructor in the initialization list. But you still need to explicitly do it.

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