当我定义一个子类只是为了抽象出基类的构造函数的细节时,它叫什么?

时间:2013-03-07 20:34:21

标签: c++ constructor subclass copy-constructor

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

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类,子类构造函数只是将适当的值(ZMQ_PUB或ZMQ_SUB)从ZeroMQ头传递给底层套接字。由于我们不能让客户端直接使用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对象。

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. 有没有办法以上述方式明确地编纂特定子类只构造函数的事实,以便编译器不允许它包含任何其他数据?

  5. 当然,如果有一个解决同样问题的根本更好的方法,我很乐意听到它。

1 个答案:

答案 0 :(得分:3)

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

<强> 5.3.5

  

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

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

实际上,它可能只是调用基类析构函数。

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

您的计划的一个优点是,如果需要,您可以输入Socket。一种不调用未定义行为(但确实有一些怪癖)的方法是定义一个与SubscribeSocket无关的Socket类,它拥有一个Socket,转发它它的构造函数,并有operator Socket&()operator Socket const&() const,允许将其传递给需要Sockets的API。当您明确需要它时,请使用.GetSocket()方法。避免operator=(Socket const&),你现在阻止切片。也许有一个明确的“create-from-socket”类型函数,可以在调试中进行检查...