Reputation: 113
I have a class Base
, and it has many derived classes: Derived1
, ...,Derived9
.
Given an instance Base* b
, is there a clean way to check whether b
is an instance of either Derived1
, ...,Derived5
?
The obvious way is to check each one of them:
if (dynamic_cast<Derived1*>(b)) return true;
// ...
if (dynamic_cast<Derived5*>(b)) return true;
return false
This seems unnecessarily repetitive.
Ideally I would like to do something like:
const auto valid_derived_classes = {Derived1,...,Derived5};
for (const auto derived_class : valid_derived_classes)
{
if (dynamic_cast<derived_class*>(b))
{
return true;
}
}
return false;
Is there a way to accomplish something like this?
For context: I want to calculate the temperature of many types of devices, where each device is assigned some sensors. When all the sensors assigned to a device fail, it should measure the temperature according to some emergency sensors. There are few types of devices that should not do this. So when calculating the temperature, I need to check if a device is exceptional.
Upvotes: 4
Views: 253
Reputation: 5457
IMHO your design is horribly broken, it's an X-Y problem and you are violating OO Design principles at every turn to try and solve it.
You have a device manager, devices, and sensors. Devices clearly have a number of sensors and potentially emergency backup sensors... Let's simplify that for designs sake of a Minimal Reproducible Example.
Device has a sensor from which it can get a temp from, or potentially a backup sensor.
Update
I really think you should take a good luck at alternatives provided by wohlstad and Pepijn Kramer. Marek R's is worth a look as well even if it has shortcomings. There are a number of ways to solve this problem, context plays a large role. I don't think there is a single clearly right answer based on what the question has 'become'.
The 'hacks' to simplify dynamic-casting or achieve the dynamic casting through another nearly identical means here while possibly achieving a short-term goal are clearly code-smell. There is a place for such things this just really does not seem to it.
End Update
Here's some rough minimal pseudo-code
class Sensor:
public:
/** returns true if got temperature, false if failed, temperature obtained populated into the argument output*/
virtual bool getTemperature(double &output);
void someOtherFunction(); // does something and updates the temperature and validity. Alternative would be that getTemperature actually does the work, returns a bool and pass in a value by refernce for updating. in which case you dont need is valid.
// for simplicity using rule of zero
private:
/** these are possible or they might not exist and be determined from device at call time **/
double temperature;
bool valid;
};
class Device:
public:
/** gets the temperature for the device however that should be done,
default is to average readings of at least as many sensors as you have
primaries of, if you have backups they will be utilized in place of primaries....
This should be overridden in inherited classes that have different needs or potentially a pure abstract
*/
virtual double getTemperature();
/** adds sensor to primary or backup set; protection from multiple adds or
adding to both sets currently left to be decided by OP
note : this could also be possibly virtual
*/
void addSensor(Sensor *sensor, bool isPrimary);
/** remove sensor from primary or backup set, again possibly
virtual
*/
void removeSensor(Sensor *sensor, bool isPrimary);
// for simplicity using rule of zero
private:
// Note these may not belong here if you choose to go the more virtual route, and leave this as an interface/abstract virtual base class
Container<std::unique_ptr<Sensor>> primarySensors; //some stl container holding pointers to sensors
Container<std::unique_ptr<Sensor>> backupSensors; //some stl container holding pointers to backup sensors
};
double Device::getTemperature()
{
//just one possible impl it could be different for different devices or not.
double temp = 0.0;
double reading = 0.0;
int count; // how many valid readings device got; if not enough get more from backups
int needed_reads = primarySensors.size(); // or this could be set in another way
for ( auto *sensor : primarySensors )
{
if ( sensor->getTemperature(reading) )
{
temp += reading;
++count;
}
}
if ( count < needed_reads )
{
for ( auto *sensor : backupSensors )
{
if ( sensor->getTemperature(reading)) {
temp += reading;
++count;
if (count == needed_reads)
break; //short circuit once we get enough readings
}
}
// take average of the readings
if ( temp != 0 && count != 0)
{
temp /= (double)count;
}
return temp;
}
/** possibly device manager should conform to device interface itself, and allow for composition but here it is not */
class DeviceManager:
public:
/** Add a device to be monitored and managed */
void addDevice(std::shared_ptr<Device> device);
/** perhaps some devices depend on others */
void linkDevice(std::shared_ptr<Device> target, std::shared_ptr<Device> dependant);
/** return true if things are ok for all devices by iterating through them and utilizing the polymorphic getStatus of each device or some other checks.
*/
bool getStatus();
// use rule of zero
// other methods and behaviors
private:
Container<std::unique_ptr<Device>> devices;
};
That sets up the model at least, the next fun is the 'business logic' The fun really belongs in where does the knowledge of acceptable ranges of state and temperature and such belong. Probably not the sensor itself it just reports readings?
Possibly in the device where the device has its rules or policy knowledge of what sensors are allowed to be in what state. Or possibly you have a composition of devices collectively for a larger device, and it has rules about the different devices ( or both of these ). And then there's how do they know about the 'rules'. This starts to get very challenging and complex fast, and isn't really the crux of the question.
But the point is that as others have mentioned, you shouldn't have to excessively know the peculiar implementation or type of the compositied object to get it's data from a class external to it. You should have a method (or callback system) such that the compositing class (be it device to a sensor, or a device manager for a number of devices) can be asked for the business case needed externally (what temperature is the device
, is the device
in an acceptable state, are there any failing sensors in the device, how many failed sensors
are there in the device
, potentially only if need be what is a given sensor
within a device
's state, etc)
The challenge lies in the variety of rules and actions to be taken for the different device types, and whether visitor patterns make more sense or fully self-contained devices and or acceptable range configurations vs an external 'checker', and the multiplicity of these things.
Upvotes: 4
Reputation: 59
Make these classes inherit from an intermediate base class, Base2
, which itself inherits from the original Base
. This way, you can achieve the same effect by checking whether Base* p
can be converted to Base2*
. This also aligns with the concept of inheritance, as these classes do share specific common traits.
Upvotes: 3
Reputation: 28049
To answer your immediate question:
Is there a way to accomplish something like this?
You can write a variadic template that uses a fold expression for the dynamic_cast
check:
#include <iostream>
#include <memory>
struct B { virtual ~B() {} };
struct D1 : public B {};
struct D2 : public B {};
struct D3 : public B {};
// Check if pointer to base (B) actually points to one of the derived (Ds):
template <typename... Ds>
bool is_one_of_derived(B* p) {
return (... || dynamic_cast<Ds*>(p));
}
int main() {
std::unique_ptr<B> p1 = std::make_unique<D1>();
// Check if p1 is actually a D1 or D2:
std::cout << std::boolalpha << is_one_of_derived<D1, D2>(p1.get()) << "\n";
std::unique_ptr<B> p3 = std::make_unique<D3>();
// Check if p3 is actually a D1 or D2:
std::cout << std::boolalpha << is_one_of_derived<D1, D2>(p3.get()) << "\n";
}
Output:
true
false
However-
As @Jarod42 commented using dynamic_cast
is usually a code-smell. You should consider an different solution, like using virtual methods.
Reading your comments under the post (and the edit with the context) regarding the sensors, you can simply add a bool
member (with a getter method), that will be set to true
in the base class, and false
in any derived that does not support it.
You can also have a virtual double GetTemperture()
method that polymorphically executes the relevant logic for each device.
Upvotes: 8
Reputation: 37462
Problem is that this you are asking question in wrong way. Question suffers from the X-Y problem.
Your proposed solution violates good OOP design.
Instead checking for list of types, you should consider correct design of base class API.
Since question suffers from X-Y problem I have to fill in some blanks.
class Base {
public:
virtual ~Base();
virtual std::string someExistingOldAction(int, int);
// add extra virtual function
virtual bool meetsSomeRequirement() const = 0;
};
With this simple correction in Base
class you can drop if (dynamic_cast<......>)
and just do:
if (basePointer->meetsSomeRequirement())
{
doSomethingSpecificForDerived1To5();
}
One choice could be to use the visitor pattern.
class BaseVisitor
{
public:
virtual ~BaseVisitor() = default;
virtual void visit(Derived1*) = 0;
virtual void visit(Derived2*) = 0;
virtual void visit(Derived3*) = 0;
virtual void visit(Derived4*) = 0;
// fallback case when forgot to add derived class above.
virtual void visit(Base*) = 0;
};
class Base {
public:
virtual ~Base();
virtual void visit(BaseVisitor& visitor) = 0;
};
class Derived1 : public Base {
public:
void visit(BaseVisitor& visitor) override
{
// not overload resolution will select respective `visit` function
visitor.visit(this);
}
};
Now if for some reason you need some special handling for some class or group of classes, you just implement BaseVisitor
and instance of that you are feeding into a Base::visit
.
For example:
class SpecialCase : public BaseVisitor
{
public:
void doSomeAction() { .... }
void doAlternativeAction() { .... }
void visit(Derived1*) override { doSomeAction(); }
void visit(Derived2*) override { doSomeAction(); }
void visit(Derived3*) override { doSomeAction(); }
void visit(Derived4*) override { doAlternativeAction(); }
void visit(Base*) override { doAlternativeAction(); }
};
void processSomething(Base *p)
{
SpecialCase visitor;
p->visit(visitor);
}
Upvotes: 1
Reputation: 12848
This answer shows you how to use virtual (abstract) baseclasses to get the desired behavior without needing to know about specific derived types.
Online demo : https://onlinegdb.com/5_nLlKR4L
#include <functional>
#include <iostream>
class TemperaturSensorItf
{
public:
virtual ~TemperaturSensorItf() = default;
virtual bool IsOk() = 0;
virtual double MeasureTemperatureInKelvin() = 0;
virtual void OnFailed(std::function<void()> callback) = 0;
};
class SensorType1 : public TemperaturSensorItf
{
public:
bool IsOk() override { return true; }
double MeasureTemperatureInKelvin() override
{
std::cout << "Backup sensor reading temperature\n";
return 312.0;
};
void OnFailed(std::function<void()>) override {};
};
class SensorTypeFailing : public TemperaturSensorItf
{
public:
SensorTypeFailing()
{
}
void Fail()
{
m_ok = false;
m_on_failed();
}
bool IsOk() override { return m_ok; }
double MeasureTemperatureInKelvin()
{
std::cout << "Sensor reading temperature\n";
return 302.0;
};
void OnFailed(std::function<void()> callback)
{
m_on_failed = callback;
}
private:
bool m_ok{ true };
std::function<void()> m_on_failed = [] {};
};
class MeasuringDevice : public TemperaturSensorItf
{
public:
MeasuringDevice(TemperaturSensorItf& sensor, TemperaturSensorItf& fallback_sensor) :
m_sensor{ sensor },
m_fallback_sensor{ fallback_sensor },
m_current_sensor{ &m_sensor }
{
m_sensor.OnFailed([&]
{
std::cout << "Sensor failure detected, switching to backup\n";
m_current_sensor = &m_fallback_sensor;
});
}
bool IsOk() override
{
return m_sensor.IsOk() && m_fallback_sensor.IsOk();
}
double MeasureTemperatureInKelvin() override
{
return m_current_sensor->MeasureTemperatureInKelvin();
}
void OnFailed(std::function<void()>) override
{
//Todo handle failure of backup sensor etc...
};
private:
TemperaturSensorItf& m_sensor;
TemperaturSensorItf& m_fallback_sensor;
TemperaturSensorItf* m_current_sensor;
};
int main()
{
SensorTypeFailing sensor;
SensorType1 backup_sensor;
MeasuringDevice device{sensor, backup_sensor};
std::cout << device.MeasureTemperatureInKelvin() << "\n";
sensor.Fail();
std::cout << device.MeasureTemperatureInKelvin() << "\n";
}
Upvotes: 2
Reputation: 76379
Declare a GetTemperatureUponFailure
a bool
method that will return false
.
Inherit all your non-exceptional devices from Device
and also inherit another class
called ExceptionalDevice
, where you override your GetTemperatureUponFailure
to return true
.
Make sure your exceptional devices are all derived from the ExceptionalDevice
class
You just call the GetTemperatureUponFailure
method to check whether it's true and if so, compute the calculation. Example:
if (device.GetTemperatureUponFailure()) {
float temperature = device.CalculateTemperature();
//do something with temperature
}
Upvotes: -1