在其他父类实现的c++接口中使用抽象虚函数

时间:2021-07-13 22:21:17

标签: c++ avr-gcc

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

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

我在这里做错了什么?

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

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

1 个答案:

答案 0 :(得分:2)

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

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

Bascy 要求的详细说明/示例...

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

要解决此问题,您可以按照 Mooing Duck 在其评论中的建议进行操作,并让派生类指定基类保存的 id。那可能是最好的。您还可以拥有一个创建光区的工厂函数,让派生类构造函数将其传递给基类以供存储/使用:

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

相关问题