我正在编写一个库,该库为两个客户端提供使用 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) {}
};
此外,广播器作为稳定端点绑定(bind)到其端口,因此需要使用
"*"
代替实际的远程地址。同样,实例化和使用广播器连接的类不需要关心这些细节,因此 BroadcasterConnection 构造函数会处理它。作为另一个例子,我对包装 ZeroMQ 套接字的类做同样的事情。我有一个基本的 Socket 类,子类构造函数只是将 ZeroMQ header 中的适当值(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) {}
所以,有几个问题:
Connection c = BroadcasterConnection(40001, 40002);
,复制构造函数被调用。由于BroadcasterConnection
没有定义任何额外的数据,这两个类应该是可以互换的(尽管有 RTTI),我们应该能够向下转换而不用担心对象切片,对吧?是否有类似方便的语法来以这种方式构造对象来避免复制?即使在构造函数初始值设定项列表中,这似乎也会发生。 Connection* c = new BroadcasterConnection(40001, 40002);
然后 delete c;
. Connection 没有虚拟析构函数,但它没有任何虚拟函数开头(所以没有 vtable)。由于 BroadcasterConnection 是 Connection 的直接子类,没有定义额外的数据,这个操作是否安全?如果 BroadcasterConnection 添加了一些成员数据呢?那么它会导致内存泄漏吗? 而且,当然,如果有一种从根本上更好的方法来解决同样的问题,我很想听听。
最佳答案
当心。您调用了未定义的行为。
5.3.5
3) In the first alternative (delete object ), if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual destructor or the behavior is undefined. [...]
我怀疑它几乎总是可以工作,但是您正在调用未定义的行为,因此要求编译器将硬盘驱动器的内容通过电子邮件发送到照片打印机,并使用您的信用卡支付费用。或者其他任何感觉。
在实践中,它可能只会调用基类析构函数。
请注意,上述大多数实用程序都可以通过返回具有派生类名称的基类对象拷贝的函数来处理。即,您有一个名为
SubscribeSocket
的函数,而不是一个名为SubscribeSocket 的类。返回 Socket
.在移动语义(您确实在 Socket
上有一个快速移动,对吗?)和 RVO(假设您愿意公开 SubscribeSocket
的实现)之间,这将是有效的。您的方案的一个优点是您可以键入
Socket
s 如果需要。一种不调用未定义行为(但确实有一些怪癖)的有效方法是定义 SubscribeSocket
与 Socket
无关的类, 它拥有 Socket
,将其构造函数转发给它,并具有 operator Socket&()
和 operator Socket const&() const
,允许将其传递给需要 Sockets
的 API .输入 .GetSocket()
明确需要时的方法。避免 operator=(Socket const&)
你现在阻止切片。也许有一个明确的“从套接字创建”类型的函数,可以在调试中检查......
关于c++ - 当我定义一个子类只是为了抽象出基类构造函数的细节时,它叫什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15281198/