虚拟继承 - 跳过构造函数

时间:2013-06-05 16:43:33

标签: c++

我有以下课程:

class Socket
{
    Socket();
    Socket( SOCKET s );
};

class Connection : public virtual Socket
{
    Connection( IP ip );
};

这两个类包含一些纯虚函数和一些非虚函数以及它们自己的一些数据。他们的意思是我将派生几种套接字类型,实现不同的协议。

所以我专注于这两个类:

class ProtocolSocket : public virtual Socket
{
    ProtocolSocket() {}
    ProtocolSocket( SOCKET s ) : Socket( s ) { ; }
};

class ProtocolConnection : public ProtocolSocket, public virtual Connection
{
    ProtocolConnection( SOCKET s, IP ip ) : ProtocolSocket( s ), Connection( ip ) {;}
};

出了点问题 - 我相信很多人都能看到。我尝试创建一个ProtocolConnection:

new ProtocolConnection( s, ip );

施工进行如下:

start ctor ProtocolConnection
    start ctor Connection
       start ctor Socket
          Socket(); - default ctor via Connection's init list
       end ctor Socket
       Connection(); - default ctor ProtocolConnection's init list
    end ctor Connection
    start ctor ProtocolSocket
       start ctor Socket     
          // Socket( s ); - skipped!!! - would have been from init 
          //                list of ProtocolSocket, but ctor for this object 
          //                already called!
       end ctor Socket
       ProtocolSocket( s ); -from init list of ProtocolConnection()
    end ctor ProtocolSocket
    ProtocolConnection( s, ip );
end ctor ProtocolConnection

跳过第二个Socket构造函数是语言规范所说的应该发生的,并且有充分的理由。

如何使用调用的Socket调用构造函数,而不是之前的调用?

我打算有多个派生类,例如OtherProtocolSocket和OtherProcolConnection,与ProtocoSocket和ProtocolConnection对象处于同一级别。

我想要实现的效果是我想构建ProtocolSocket和ProtocolConnection对象,然后将它们作为Socket和Connection对象传递给我的系统。因此,在我创建了一个套接字后,实现了一个给定的协议,我只是读取并写入它,而不必担心底层协议的细节。

连接对象需要继承Socket对象的所有方法。

@UPDATE:

DyP建议在ProtocolConnection中为Socket添加初始化程序。这解决了这个问题。我会给你一个接受......但它只是在评论中。

2 个答案:

答案 0 :(得分:3)

要记住的关键是虚拟基类的构造函数是作为最派生类初始化的一部分(并且在构造其他基类之前)完成的。因此您的施工订单幻灯片不正确。

实际上,构造ProtocolConnection时发生的事情是它首先构造Socket,然后是Connection(因为你虚拟地继承了它),最后是ProtcolSocket。

要调用所需的socket构造函数,需要将其构造函数作为ProtocolSocket成员初始化列表的一部分调用,如此

class ProtocolConnection: public ProtocolSocket, public virtual Connection
{
    public:
    ProtocolConnection(int s, int ip) :
        Socket(s), Connection(ip), ProtocolSocket(s)  
        // Note, also reordered, since all virtual bases are initialized before the
        // non-virtual bases
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

最后,作为一个说明,我建议简化继承层次结构。特别是,虚拟继承和使用多个构造函数会使因素复杂化。

答案 1 :(得分:2)

继承DAG:

       ProtocolConnection
           /        \
     non-virtual  virtual
         /            \
ProtocolSocket     Connection
       |               |
    virtual         virtual
       |               |
    Socket           Socket

请注意,由于虚拟继承,Socket类型的对象中只有一个ProtocolConnection子对象。

[class.base.init] / 10

  

首先,仅对于派生程度最高的类(1.8)的构造函数,虚拟基类按照它们出现在基类的有向无环图的深度优先从左到右遍历的顺序进行初始化,其中“从左到右”是派生类base-specifier-list中基类出现的顺序。

虚拟基类的初始化是通过深度优先的从左到右遍历完成的。遍历顺序:

       (0) ProtocolConnection
             /             \
           nv               v
           /                 \
(1) ProtocolSocket    (3) Connection
         |                   |
         v                   nv
         |                   |
    (2) Socket         (4) Socket

导致初始化顺序为:

(2); (3); (1); (0)
Socket; Connection; ProtocolSocket(非虚基类); ProtocolConnection

派生程度最高的班级ProtocolConnection必须包含所有 虚拟基类的初始化程序。如果虚拟基类未出现在派生程度最高的类的mem-initializer-list中,则此虚拟基类的子对象将默认构造。