使用C ++,如何正确地从同一个基类继承两次?

时间:2010-01-01 23:34:13

标签: c++ multiple-inheritance

这是我们理想的继承层次结构:

class Foobar;

class FoobarClient : Foobar;

class FoobarServer : Foobar;

class WindowsFoobar : Foobar;

class UnixFoobar : Foobar;

class WindowsFoobarClient : WindowsFoobar, FoobarClient;

class WindowsFoobarServer : WindowsFoobar, FoobarServer;

class UnixFoobarClient : UnixFoobar, FoobarClient;

class UnixFoobarServer : UnixFoobar, FoobarServer;

这是因为我们的继承层次结构会尝试从Foobar继承两次,因此编译器会抱怨对Foobar的任何成员的模糊引用。

请允许我解释为什么我想要这么复杂的模型。这是因为我们希望可以从WindowsFoobarUnixFoobarFoobarClientFoobarServer访问相同的变量。这不会是一个问题,只是我想使用上面任意组合的多重继承,这样我就可以在任何平台上使用服务器/客户端功能,并在客户端或服务器上使用平台功能。

我不禁觉得这是多重继承的一个常见问题...我是从完全错误的角度来解决这个问题吗?

更新1:

另外,请考虑我们可以使用#ifdef来解决这个问题,但是,这会产生非常难看的代码:

CFoobar::CFoobar()
#if SYSAPI_WIN32
: m_someData(1234)
#endif
{
}

...哎呀!

更新2:

对于那些想要更多地了解这个问题的人,我真的建议略过适当的mailing list thread。在第3篇文章开始变得有趣。还有一个related code commit,你可以在这里看到现实生活中的代码。

8 个答案:

答案 0 :(得分:18)

可以工作,但你会得到基础Foobar类的两个副本。要获得单个副本,您需要使用虚拟继承。阅读多重继承here

class Foobar;

class FoobarClient : virtual public Foobar;

class FoobarServer : virtual public Foobar;

class WindowsFoobar : virtual public Foobar;

class UnixFoobar : virtual public Foobar;

但是,多重继承存在许多问题。如果您真的想要展示模型,为什么不让FoobarClientFoobarServer在施工时引用Foobar,然后Foobar& FoobarClient/Server::getFoobar

组合通常是多重继承的一种方式。现在举个例子:

class WindowsFoobarClient : public WindowsFoobar 
{
    FoobarClient client;
public:
    WindowsFoobarClient() : client( this ) {}
    FoobarClient& getClient() { return client }
}

但是care must be taken在构造函数中使用它。

答案 1 :(得分:7)

这里直接的是C ++的虚拟继承功能。你在这里的是维护噩梦。这可能不是一个巨大的惊喜,因为像H. Sutter这样的着名作家已经反对这种继承的使用已经有一段时间了。但这来自对此类代码的直接体验。避免深度继承链。非常害怕protected关键字 - 它的使用非常有限。这种设计很快就会失控 - 跟踪受保护变量的访问模式,从较低级别的类继承链变得困难,代码部分的责任变得模糊等等,以及那些查看代码一年的人从现在开始会恨你:)。

答案 2 :(得分:5)

你是C ++,你应该对模板友好。使用template-argument-is-a-base-class模式,您不需要任何多重继承或冗余实现。它看起来像这样:

class Foobar {};

template <typename Base> class UnixFoobarAspect : public Base {};
template <typename Base> class WindowsFoobarAspect : public Base {};
template <typename Base> class FoobarClientAspect : public Base {};
template <typename Base> class FoobarServerAspect : public Base {};

typedef UnixFoobarAspect<FoobarClientAspect<Foobar>/*this whitespace not needed in C++0x*/> UnixFoobarClient;
typedef WindowsFoobarAspect<FoobarClientAspect<Foobar> > WindowsFoobarClient;
typedef UnixFoobarAspect<FoobarServerAspect<Foobar> > UnixFoobarServer;
typedef WindowsFoobarAspect<FoobarServerAspect<Foobar> > WindowsFoobarServer;

当基类需要调用在一个专用变体中实现的函数时,您也可以考虑使用奇怪的重复模板模式而不是声明抽象函数来避免虚函数调用。

答案 3 :(得分:3)

使用虚拟继承,在FoobarClientFoobarServerWindowsFoobarUnixFoobar的声明中,将virtual放在Foobar之前基类名称。

这将确保始终存在Foobar的单个实例,无论它出现在基类层次结构中的次数。

答案 4 :(得分:2)

看看这个search。钻石继承是一个含糊不清的问题,适当的解决方案取决于个人情况。

我想评论Unix / Windows方面的事情。通常会有#ifndef个问题不适合特定平台。因此,您最终只能使用预处理程序指令为Windows或Unix编译Foobar,而不是UnixFoobarWindowsFoobar。在探索虚拟继承之前,了解使用该范例可以获得多大的帮助。

答案 5 :(得分:2)

尝试这个组合和继承的例子:

class Client_Base;
class Server_Base;

class Foobar
{
  Client_Base * p_client;
  Server_Base * p_server;
};

class Windows_Client : public Client_Base;
class Windows_Server : public Server_Base;

class Win32 : Foobar
{
  Win32()
  {
    p_client = new Windows_Client;
    p_server = new Windows_Server;
  }
};

class Unix_Client : public Client_Base;
class Unix_Server : public Server_Base;

class Unix : Foobar
{
  Unix()
  {
    p_client = new Unix_Client;
    p_server = new Unix_Server;
  }
};

许多专家表示,问题可以通过另一层次的间接解决。

答案 6 :(得分:1)

两次使用相同的基类没有任何“非法”。最后的子类将(字面上)具有基类的多个副本作为其一部分(包括基类中的每个变量等)。但是,它可能会导致对基类函数的一些模糊调用,您可能需要手动解析它们。这听起来不像你想要的那样。

考虑构图而不是继承。

此外,虚拟继承是将出现两次的相同基类折叠在一起的一种方法。但是,如果真的只是数据共享,那么组合可能更有意义。

答案 7 :(得分:0)

您可以使用限定类名访问变量,但我忘记了确切的语法。

但是,这是使用多重继承的不良情况之一,可能会给您带来很多困难。有可能你不想这样做。

你更希望私有继承foobar,让每个子类拥有一个foobar,让foobar成为一个纯虚拟类,或让派生类拥有它当前定义的东西,甚至自己定义foobar。 / p>