C ++从多个选项中实例化子类

时间:2015-01-27 00:43:23

标签: c++

我正在使用一个实现不同类型网络协议的库,如下面的简化示例,希望能够说明我遇到的问题。注意:这只是为了显示整体问题的伪代码。

class Network
{
  virtual void connect() {...}
  void readPacket() = 0;
};

class NetworkClient : public Network
{
  virtual void connect(int ip, int port) {super::connect() ...}
};

class NetworkServer : public Network
{
  virtual void connect(int port) {super::connect() ...}
};

class ProtocolAClient : public NetworkClient
{
  void readPacket() {...}
};

class ProtocolAServer : public NetworkServer
{
  void readPacket() {...}
};

class ProtocolBClient : public NetworkClient
{
  void readPacket() {...}
};

class ProtocolBServer : public NetworkServer
{
  void readPacket() {...}
};

现在在我的应用程序中,我希望有一个客户端和一个服务器,它应该是ProtocolA或ProtocolB客户端/服务器,具体取决于用户选择连接的协议。

所以我认为我可以像这样创建我的应用程序类。

class AppClient : public NetworkClient
{
   ... custom functionality needed by the app client ...
   void sendAppData(...)
};

class AppServer : public NetworkServer
{
   ... custom functionality needed by the app server ...
   void sendAppData(...)
};

然后我想当我需要一个客户端时,例如,在应用程序中我可以这样做。

AppClient *client;
if(useProtocalA)
  client = new ProtocolAClient;
else
  client = new ProtocolBClient;
client->sendAppData();

但编译器很快就让我知道这是不可能的,因为ProtocolAClient和ProtocolBClient不是AppClient。这是确切的编译器错误。

error C2440: '=' : cannot convert from 'ProtocolAClient *' to 'AppClient *'

所以我的下一个想法是创建AppClient和AppServer模板类,但这不起作用,因为你不能获得指向实例的指针,因为指针没有像这样的模板参数。

AppClient *client; <--- Uh oh... missing template argument!
if(useProtocalA)
  client = new AppClient<ProtocolAClient>;
else
  client = new AppClient<ProtocolBClient>;
client->sendAppData();

这似乎应该是一个很难解决的问题,但我似乎无法看到解决方案。

3 个答案:

答案 0 :(得分:1)

所以你有以下课程:

class Network
{
  virtual void connect() {...}
  void readPacket() = 0;
}

class NetworkClient : public Network
{
  virtual void connect(int ip, int port) {super::connect() ...}
}

class AppClient : public NetworkClient
{
   ... custom functionality needed by the app client ...
   void sendAppData(...)
}

class ProtocolAClient : public NetworkClient
{
  void readPacket() {...}
}

class ProtocolBClient : public NetworkClient
{
  void readPacket() {...}
}

问题是您需要AppClient个对象,但ProtocolAClient的类型为NetworkClient,而不是AppClient类型。

你的继承看起来像这样:

Network ---- NetworkClient ---- ProtocolAClient
                          |---- ProtocolBClient
                          |---- AppClient

正如您所看到的,ProtocolAClientProtocolBClient都不属于AppClient类型,但所有类型都属于NetworkClient类型。

因此,如果您希望此代码有效:

AppClient *client;
if(useProtocalA)
  client = new ProtocolAClient;
else
  client = new ProtocolBClient;
client->sendAppData();

您必须执行以下更改之一:

A)更改您的ProtocolAClientProtocolBClient以继承AppClient类:

class ProtocolAClient : public AppClient
{
  void readPacket() {...}
}

class ProtocolBClient : public AppClient
{
  void readPacket() {...}
}

现在你的继承看起来像这样:

Network ---- NetworkClient ---- AppClient ---- ProtocolAClient
                                         |---- ProtocolBClient

或B) - 推荐:不要使用AppClient,因为你已经有了NetworkClient:

NetworkClient*client;
if(useProtocalA)
  client = new ProtocolAClient;
else
  client = new ProtocolBClient;
client->sendAppData();

答案 1 :(得分:1)

我想我会立刻停下来:

class Network {};
class NetworkClient : public Network {};
class NetworkServer : public Network {};
乍一看,这看起来像是一个严重的问题。公共继承意味着Liskov替换原则 - 您应该能够在需要基类的实例的任何地方替换派生类的实例。

NetworkClient(或NetworkServer)不是网络,我怀疑甚至一个情况,其中任何一个都可以替代网络(更不用说所有可能的情况) 。因此,在我看来,您的基本设计从一开始就存在问题。

我的直接反应是你可能想要这样的东西:

class ProtocolA {};
class ProtocolB {};

template <class Protocol>
class Client {};

template <class Protocol>
class Server {};

通过这种方式,您可以使用任一协议轻松实例化客户端或服务器。但这不支持 ,支持运行时替换。也就是说,你不会得到类似基类的东西,它可以让你完全透明地(在运行时)处理可能是两种或更多种类型的对象。

如果你真的需要,你可能想要更像的东西:

class Protocol {};

class ProtocolA : public Protocol {};

class ProtocolB : public Protocol {};

class Client {
    Protocol *p;
public:
    Client(Protocol *p) : p(p) {}
};

class Server {
    Protocol *p;
public:
    Server(Protocol *p) : p(p) {}
};

[旁白:尽管合理地遵循了语法,但这实际上是伪代码而不是C ++。在代码实际可以使用之前,您显然需要添加许多其他内容,例如虚拟析构函数。]

然后,您实例化一个正确的Protocol实例,并传递(指向该实例)以创建一个Client或Server对象。

使用其中任何一个,客户端/服务器使用协议进行通信,但(与公共继承不同)我们没有做出关于能够替换客户端/服务器的无意义断言网络(或网络协议)。

答案 2 :(得分:0)

对我来说,你的类层次结构没有精确定义。特别是:NetworkClient类到底是什么以及你打算用它做什么?如果要使用它以便必须调用client->sendAppData(),那么ProtocolAClient类不应该从中派生。你给出的这个解决方案似乎是最好的:

AppClient *client; <--- Uh oh... missing template argument!
if(useProtocalA)
  client = new AppClient<ProtocolAClient>;
else
  client = new AppClient<ProtocolBClient>;
client->sendAppData();

但是你说你没有指定协议客户端类就无法拥有这个AppClient

您可以使用虚拟模板对习惯用法(或其使用的任何名称)来解决此问题:

class AppClient { /* ... */ virtual void sendAppData() = 0; };

template <class ProtocolClientClass>
class AppClientSpec: public AppClient
{
     /* ... */
     ProtocolClientClass* inproto; // or whatever...
     virtual void sendAppData() { return inproto->sendAppData(); }
};

现在您的示例可以修复为:

AppClient *client; <--- Uh oh... missing template argument!
if(useProtocalA)
  client = new AppClientSpec<ProtocolAClient>;
else
  client = new AppClientSpec<ProtocolBClient>;
client->sendAppData();