PlsHelp
PlsHelp

Reputation: 113

Check if an instance of base class is one of some derived classes

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

Answers (6)

UpAndAdam
UpAndAdam

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

sheldon5
sheldon5

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

wohlstad
wohlstad

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

Live demo

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

Marek R
Marek R

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

Pepijn Kramer
Pepijn Kramer

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

Lajos Arpad
Lajos Arpad

Reputation: 76379

1. The basis of temperature calculation

Declare a GetTemperatureUponFailure a bool method that will return false.

2. Single source of truth

Inherit all your non-exceptional devices from Device and also inherit another class called ExceptionalDevice, where you override your GetTemperatureUponFailure to return true.

3. Exceptional device subclasses

Make sure your exceptional devices are all derived from the ExceptionalDevice class

Usage

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

Related Questions