12

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

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.

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

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

更新1:

另外,考虑到我们可以用#ifdef它来解决这个问题,但是,这往往会产生非常丑陋的代码,如下所示:

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

......糟糕!

更新 2:

对于那些想要了解更多关于这个问题的背景的人,我真的建议浏览相应的邮件列表线程。事情开始在第 3 篇文章中变得有趣起来。还有一个相关的代码提交,您可以在这里看到有问题的真实代码。

4

8 回答 8

18

Foobar工作,虽然你会得到两个基类的副本。要获得单个副本,您需要使用虚拟继承。在此处阅读多重继承。

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

但是,在构造函数中使用 this 时必须小心

于 2010-01-01T23:41:59.073 回答
7

你在这里直接追求的是C++ 的虚拟继承特性。你在这里的目的是一场维护噩梦。这可能并不令人意外,因为像 H. Sutter 这样的知名作者已经反对这种继承的使用已经有一段时间了。但这来自对此类代码的直接经验。避免深度继承链。非常害怕protected关键字 - 它的使用非常有限。这种设计很快就失控了——从低级类的继承链上的某个地方追踪对受保护变量的访问模式变得很困难,代码部分的职责变得模糊等等,而且每年查看你代码的人从现在开始会恨你:)

于 2010-01-02T00:02:04.740 回答
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;

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

于 2010-01-02T01:46:16.063 回答
3

使用虚拟继承,在、和的声明中FoobarClient,将单词放在基类名称之前。FoobarServerWindowsFoobarUnixFoobarvirtualFoobar

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

于 2010-01-01T23:38:46.333 回答
2

看看这个搜索。钻石继承是一个有争议的问题,适当的解决方案取决于个人情况。

我想评论一下 Unix/Windows 方面的事情。通常,人们会#ifndef发现不适合特定平台的东西。所以你最终会Foobar使用预处理器指令为 Windows 或 Unix 编译,而不是UnixFoobarWindowsFoobar. 在探索虚拟继承之前,看看您可以使用该范式多远。

于 2010-01-01T23:51:05.017 回答
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;
  }
};

许多专家表示,问题可以通过另一个级别的间接来解决。

于 2010-01-02T00:53:28.307 回答
1

两次拥有相同的基类并没有什么“非法”的。最后一个子类将只是(字面上)具有基类的多个副本作为它的一部分(包括基类中的每个变量等)。但是,它可能会导致对该基类函数的一些模棱两可的调用,您可能必须手动解决。这听起来不像你想要的。

考虑组合而不是继承。

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

于 2010-01-01T23:37:54.803 回答
0

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

然而,这是使用多重继承的糟糕案例之一,可能会给您带来很多困难。很有可能你不想这样。

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

于 2010-01-02T04:33:23.713 回答