0

我正在编写一个库,该库为两个客户端提供使用 ZeroMQ PUB/SUB 套接字进行通信的能力。每个客户端应用程序实例化广播者端点或接收者端点,并且这些端点类有一个 Connection 成员:

class Connection {
    Connection(const char* address, int outgoingPort, int incomingPort);
};

Connection 有几个套接字,并被配置为通过各自的端口连接到给定的地址。但是,我不想在实际实例化 Connection 的类中公开这些细节。基本连接对象有一个传入端口和一个传出端口,但是这个细节不需要渗透到程序的其余部分。在那些更高级别的层中,考虑两个指定端口,数据端口和控制端口会更明智。所以我有两个子类,它们实现构造函数来定义哪个端口是传入端口,哪个是该特定连接类型的传出端口。

class BroadcasterConnection : public Connection {
    BroadcasterConnection(int dataPort, int controlPort)
    :Connection("*", dataPort, controlPort) {}
};
class ReceiverConnection : public Connection {
    ReceiverConnection(const char* hostAddress, int dataPort, int controlPort)
    :Connection(hostAddress, controlPort, dataPort) {}
};

此外,广播器作为稳定端点绑定到其端口,因此需要使用它来"*"代替实际的远程地址。同样,实例化和使用广播器连接的类不需要关心这些细节,因此 BroadcasterConnection 构造函数会处理它。

作为另一个例子,我对包装 ZeroMQ 套接字的类做同样的事情。我有一个基本的 Socket 类,子类构造函数只是将 ZeroMQ 标头中的适当值(ZMQ_PUB 或 ZMQ_SUB)传递到底层套接字。由于我们不能让客户端直接使用来自 ZeroMQ 的值,因此我们需要以某种正式的方式对 PUB 套接字和 SUB 套接字之间的区别进行编码,并且提供单个子类构造函数似乎是一种透明且明智的做法。

class Socket:
    Socket(void* context, const char* address, int port, int socketType);

class PublishSocket : public Socket:
    PublishSocket(void* context, const char* address, int port)
    :Socket(context, address, port, ZMQ_PUB) {}

class SubscribeSocket : public Socket:
    SubscribeSocket(void* context, const char* address, int port)
    :Socket(context, address, port, ZMQ_SUB) {}

这些子类根本没有做任何花哨的事情,但我希望您会同意它们是抽象服务中有用且健康的补充。但我不知道这个简单成语的通用名称。当我定义一个实现构造函数的子类时,仅仅为了构造一个具有更专业的参数集的对象,我在做什么?

这里的关键点是这些子类没有定义任何额外的方法或数据。这是另一个示例,其中有一个标记基类,可标识任何类型的任何实体。子类用于根据一些特定于域的参数为各个类型的实体创建标签,但最终它们都归结为 Tag 对象。

Tag(char typeIdentifier, int entityIdentifier);

LightTag(int lightIndex):Tag('L', lightIndex) {}
SkeletonTag(const char* skeletonName):Tag('S', hash(skeletonName)) {}
CameraTag():Tag('C', 0) {}

所以,有几个问题:

  1. 这个成语有一个常用的、可在 Google 上搜索的名称吗?

  2. 如果我写Connection c = BroadcasterConnection(40001, 40002);,复制构造函数被调用。由于BroadcasterConnection没有定义任何额外的数据,这两个类应该是可互换的(尽管有 RTTI),我们应该能够向下转换而不用担心对象切片,对吧?是否有类似方便的语法来以这种方式构造对象来避免复制?即使在构造函数初始值设定项列表中,这似乎也会发生。

  3. 这是一个不太实际的例子,但假设我写Connection* c = new BroadcasterConnection(40001, 40002);然后delete c;. Connection 没有虚拟析构函数,但它没有任何虚拟函数开头(所以没有 vtable)。由于 BroadcasterConnection 是 Connection 的直接子类,没有定义额外的数据,这个操作是否安全?如果 BroadcasterConnection 添加了一些成员数据呢?那么它会导致内存泄漏吗?

  4. 有没有办法以上述方式明确编码特定子类仅是构造函数的事实,以便编译器不允许它包含任何附加数据?

而且,当然,如果有一种从根本上更好的方法来解决同样的问题,我很想听听。

4

1 回答 1

3

当心。您调用了未定义的行为。

5.3.5

3)在第一种选择(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数动态类型的基类,并且静态类型应具有虚拟析构函数或行为未定义。[...]

我怀疑它几乎总是可以工作,但是您正在调用未定义的行为,因此要求编译器将硬盘驱动器的内容通过电子邮件发送到照片打印机,并使用您的信用卡支付费用。或者其他任何感觉。

在实践中,它可能只会调用基类析构函数。

请注意,上述大多数实用程序都可以通过返回具有派生类名称的基类对象副本的函数来处理。即,您有一个名为的函数,而不是一个名为 SubscribeSocket 的类,SubscribeSocket它返回一个Socket. 在移动语义(你确实有一个快速移动Socket,对吗?)和 RVO(假设你愿意公开 的实现SubscribeSocket)之间,这将是有效的。

您的方案的一个优点是您可以Socket在需要时键入您的 s。一种不调用未定义行为(但确实有一些怪癖)的有效方法是定义一个SubscribeSocket与 无关的类,该类Socket拥有 a Socket,将其构造函数转发给它,并具有operator Socket&()and operator Socket const&() const,允许将其传递给需要Sockets. 当你明确需要它时,抛出一个.GetSocket()方法。避免operator=(Socket const&),你现在阻止切片。也许有一个明确的“从套接字创建”类型的函数,可以在调试中检查......

于 2013-03-07T20:42:36.937 回答