C++ 什么';当我定义一个子类只是为了抽象出基类的细节时,它就被调用了;谁的构造函数?

C++ 什么';当我定义一个子类只是为了抽象出基类的细节时,它就被调用了;谁的构造函数?,c++,constructor,subclass,copy-constructor,C++,Constructor,Subclass,Copy Constructor,我正在编写一个库,它为两个客户端提供了使用ZeroMQ发布/订阅套接字进行通信的能力。每个客户端应用程序实例化广播机端点或接收器端点,这些端点类具有一个连接成员: class Connection { Connection(const char* address, int outgoingPort, int incomingPort); }; 连接有两个套接字,配置为通过各自的端口连接到给定的地址。但是,我不想在实际实例化连接的类中公开这些细节。基本连接对象有一个传入端口和一个传出端口

我正在编写一个库,它为两个客户端提供了使用ZeroMQ发布/订阅套接字进行通信的能力。每个客户端应用程序实例化广播机端点或接收器端点,这些端点类具有一个连接成员:

class Connection {
    Connection(const char* address, int outgoingPort, int incomingPort);
};
连接有两个套接字,配置为通过各自的端口连接到给定的地址。但是,我不想在实际实例化连接的类中公开这些细节。基本连接对象有一个传入端口和一个传出端口,但是这个细节不需要贯穿程序的其余部分。在那些更高层次的层中,考虑两个指定的端口,数据端口和控制端口会更明智。因此,我有两个子类,它们实现构造函数来定义哪个端口是传入端口,哪个端口是特定类型连接的传出端口

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) {}
};
此外,广播公司将其端口绑定为稳定的端点,因此需要使用
“*”
代替实际的远程地址。同样,实例化和使用广播连接的类不需要关心这个细节,所以广播连接构造函数会处理它

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

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(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(400014002),将调用复制构造函数。由于
    BroadcasterConnection
    没有定义任何附加数据,因此这两个类应该是可互换的(尽管有RTTI),我们应该能够进行向下转换,而不必担心对象切片,对吗?以这种方式构造对象是否有类似的方便语法来避免复制?即使在构造函数初始值设定项列表中也会发生这种情况

  • 这是一个不太实际的例子,但假设我编写
    Connection*c=new-BroadcasterConnection(400014002)然后
    删除c。连接没有虚拟析构函数,但它没有任何虚拟函数(因此没有vtable)。由于BroadcastConnection是Connection的一个直接子类,不定义任何附加数据,因此此操作是否安全?如果广播连接添加了一些成员数据会怎么样?这样会导致内存泄漏吗

  • 是否有任何方法可以显式地将特定子类仅以上述方式作为构造函数的事实进行编码,以便编译器不允许它包含任何附加数据


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

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

    5.3.5

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

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

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

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

    您的方案的一个优点是,如果需要,您可以键入
    套接字。一种不调用未定义行为(但确实有一些怪癖)的方法是定义一个与
    Socket
    无关的
    SubscribeSocket
    类,它拥有一个
    Socket
    ,将其构造函数转发给它,并具有
    运算符Socket&()
    运算符Socket const&()
    ,允许将其传递给需要
    套接字的API
    。当您明确需要时,可以使用
    .GetSocket()
    方法。避免
    operator=(Socket const&)
    ,现在就可以阻止切片了。可能有一个显式的“createfromsocket”类型的函数,用于在调试中进行检查…

    小心