Im trying to define an interface (abstract class) which will "automatically" register any instance created to a global map, where the key is an uint8_t
and the value is a pointer to the interface class.
All classes that will implement this interface already have a method to retrieve a unique id with a getId()
method. I've tried the following approach, but it get warningsn when I use the (then) abstract method getId() in the c'tor and d'tor of the interface, which I can understand. But I get an error when I try to create an instance of LightZoneImpl because it has no implementation of getId().
What am I doing wrong here?
Note: This is a simplified example of the real thing, lots of other classes etc involved in the real thing.
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
Note2: The example below is modified to show the solution suggested below
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
Virtual dispatch doesn't start using the derived-class function overrides until construction completes: at the time you hoped getId()
would use the derived-class override, only part of the abstract base class had been constructed - the derived object didn't exist to have its functions called.
You can have derived classes or a factory function provide the id and operate on the map.
Elaboration/example as requested by Bascy...
You can think of the objects involved here as being a LightZoneImpl
object in which an ILightZone
base class object is embedded. To construct the LightZoneImpl
, the base class must be constructed first... and while that's happening the derived-class object doesn't exist or have the invariants (guarantees about state) that the derived-class constructor sets up, so it's premature to call any derived class overrides of the virtual functions. For that reason, the C++ Standard says the base class virtual function implementations should continue to be called, but if they're unavailable because the function is pure virtual your program will terminate.
To work around this, you can do what Mooing Duck suggests in his comment, and have the derived class specify an id that the base class saves. That's probably best. You could also have a factory function that creates light zones, letting the derived-class constructor pass it down to the base 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_++);
}
You'd then want to make the light zone constructors private and make the factory a friend
.