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

发布于 2024-10-01 18:05:51 字数 622 浏览 7 评论 0原文

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

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

struct B
{
   B(int);
};

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

一些供应商建议,使用右值引用和可变参数模板(完美的转发构造函数),在继承类中提供转发构造函数将很简单,从而避免继承构造函数。

例如:

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

我有两个问题:

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

2)您认为是否存在任何技术原因导致“完美转发构造函数”无法成为适当的替代方案?

谢谢!

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

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.

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?

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

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

深者入戏 2024-10-08 18:05:51

2)您认为是否存在任何技术原因导致“完美转发构造函数”无法成为适当的替代方案?

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

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

另一个问题是初始化列表构造函数,因为您无法将 Args 推导为 initializer_list。相反,您需要使用 B{args...} 转发到基类(注意大括号),并使用 (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] (!!)

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

I have shown one problem with that perfect forwarding approach here: Forwarding all constructors in 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:

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] (!!)
水染的天色ゝ 2024-10-08 18:05:51

所提出的解决方法有几个缺点:

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

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

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

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.

乖乖 2024-10-08 18:05:51

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

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

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

干杯&嗯。

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.

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

Cheers & hth.

风筝有风,海豚有海 2024-10-08 18:05:51

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

也就是说:

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

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

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?

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.

柒夜笙歌凉 2024-10-08 18:05:51

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文