在由其他父级实现的 C++ 接口中使用抽象虚函数 class

Using abstract virtual function in c++ Interface implemented by other parent class

我正在尝试定义一个接口(抽象 class),它将“自动”将创建的任何实例注册到全局映射,其中键是 uint8_t,值是指向的指针界面 class.

所有将实现此接口的 class 都已经具有使用 getId() 方法检索唯一 ID 的方法。我尝试了以下方法,但是当我在接口的 c'tor 和 d'tor 中使用 (then) 抽象方法 getId() 时,它会收到警告,我可以理解。但是当我尝试创建 LightZoneImpl 的实例时出现错误,因为它没有 getId() 的实现。

我做错了什么?

注意:这是真实事物的简化示例,真实事物中涉及许多其他 classes 等。

class ILightZone; // forward
typedef std::map<uint8_t, ILightZone*> LightZoneMap;
extern LightZoneMap lightZoneMap;
      
/**
 * @brief Interface defining a Lightzone operating node, automatically (de-)registered in the lightZoneMap
 * 
 */
class ILightZone {
public:
  ILightZone() {
    lightZoneMap[getId()] = this; // <== warning: pure virtual function called from c'tor
  }

  virtual ~ILightZone() {
    lightZoneMap.erase(getId()); // <== warning: pure virtual function called from d'tor
  }
  virtual const uint8_t getId() const = 0;
  virtual void setLightOn() = 0;
  virtual void setLightOff() = 0;
  virtual bool isLightOn() = 0;
  virtual void setIntensity(const uint8_t percentage) = 0;
  virtual uint8_t getIntensity() = 0;
};

class BaseNode {
public:
  BaseNode(uint8_t nodeId) : nodeId(nodeId) {};
  virtual ~BaseNode() {};
  virtual const uint8_t getid() const { return nodeId; };
private:
  uint8_t nodeId;
};

class LightZoneImpl : public ILightZone, public BaseNode {
public:
  LightZoneImpl() {};
  virtual ~LightZoneImpl() {};

  using BaseNode::getId;

  void setLightOn() override { /* implementation */};
  void setLightOff() override { /* implementation */};
  bool isLightOn() override { return false; };
  void setIntensity(const uint8_t percentage) override { /* implementation */ };
  uint8_t getIntensity() override { return 0; };
}

LightZoneImpl zone{12}; // <= error: cannot declare variable 'zone' to be of abstract type LightZoneImpl

注 2:修改了下面的示例以显示下面建议的解决方案

class ILightZone; // forward
typedef std::map<uint8_t, ILightZone*> LightZoneMap;
extern LightZoneMap lightZoneMap;
      
/**
 * @brief Interface defining a Lightzone operating node, automatically (de-)registered in the lightZoneMap
 * 
 */
class ILightZone {
public:
  ILightZone(uint8_t nodeId) : nodeId(nodeId) {
    lightZoneMap[nodeId] = this; 
  }

  virtual ~ILightZone() {
    lightZoneMap.erase(nodeId); 
  }
  const uint8_t getId() const { return nodeId; };
  virtual void setLightOn() = 0;
  virtual void setLightOff() = 0;
  virtual bool isLightOn() = 0;
  virtual void setIntensity(const uint8_t percentage) = 0;
  virtual uint8_t getIntensity() = 0;
private:
  uint8_t nodeId;
};

class BaseNode {
public:
  BaseNode(uint8_t nodeId) : nodeId(nodeId) {};
  virtual ~BaseNode() {};
  virtual const uint8_t getid() const { return nodeId; };
private:
  uint8_t nodeId;
};

class LightZoneImpl : public ILightZone, public BaseNode {
public:
  LightZoneImpl(uint8_t nodeId) : ILightZone(nodeId), BaseNode(nodeId) {};
  virtual ~LightZoneImpl() {};

  using BaseNode::getId;

  void setLightOn() override { /* implementation */};
  void setLightOff() override { /* implementation */};
  bool isLightOn() override { return false; };
  void setIntensity(const uint8_t percentage) override { /* implementation */ };
  uint8_t getIntensity() override { return 0; };
}

LightZoneImpl zone{12}; // <= error: cannot declare variable 'zone' to be of abstract type LightZoneImpl

虚拟调度在构造完成之前不会开始使用派生-class 函数覆盖:当时您希望 getId() 将使用派生-class 覆盖,只是部分抽象的 base class 已经构造 - 派生对象不存在以调用其函数。

您可以派生 classes 或工厂函数提供 id 并在地图上操作。

Elaboration/example 根据 Bascy 的要求...

你可以认为这里涉及的对象是一个LightZoneImpl对象,其中嵌入了一个ILightZone基础class对象。要构建 LightZoneImpl,必须首先构建基础 class... 而当发生这种情况时,派生的 class 对象不存在或不具有不变量(关于状态的保证) derived-class 构造函数已设置,因此调用虚函数的任何派生 class 覆盖为时过早。出于这个原因,C++ 标准说基本 class 虚函数实现应该继续被调用,但是如果它们不可用,因为函数是纯虚函数,你的程序将终止。

要解决此问题,您可以按照 Mooing Duck 在其评论中的建议进行操作,并让派生的 class 指定基 class 保存的 ID。这可能是最好的。您还可以有一个创建光区的工厂函数,让 derived-class 构造函数将它传递给基础 class for storage/use:

std::unique_ptr<LightZoneImpl> lz_factory() {
    static int id_ = 0;
    if (id_ > 255)
        throw std::runtime_error("too many lightzones");
    return std::make_unique<LightZoneImpl>(id_++);
}

然后您希望将光区构造函数设为私有并将工厂设为 friend