c++castingembeddedshared-ptrrtti

Casting smartpointers without RTTI C++


I am trying to manage devices on an embedded device. The devices are managed by the device manager. Its responsibility is to ensure the lifetime of the devices and supply the other parts of the code with devices.

A device has certain methods (init, check status, etc.) For simplicity, I only added IsReady(). In order to make the code more flexible, I like to use interfaces. For example, I have an IO expander that has UART support. So this device implements IDevice but also the IStream interface.

I can ask for all devices that are compatible with IStream. Since I don't want to rely on RTTI, I use keys for this. (In the example these are hardcoded) I would prefer if this wasn't required, but without RTTI I don't see that happen.

The problem arises when I try to get devices with a certain interface. Since this interface itself doesn't inherit from IDevice, I don't have access to the IDevice methods.

Here is my question: I have the object, that is a std::shared_ptr<IStream> now I would like to access the methods that are defined by IDevice. Since IStream doesn't implement IDevice, I can't access these methods. For the same reason, I can't use static _cast, and because I don't have RTTI, I can't use dynamic_cast.

One solution that just popped in my head, is using the devicemanager to get the IDevice from the IStream.

Here is the stripped down version of what I try to achieve:


class IDevice {
public:    
    virtual bool IsReady() = 0;
};

class IStream {
public:
    virtual size_t ReadStream(uint8_t* buffer, size_t bufferSize) = 0;
};

class DeviceManager {
public:
    template<typename T>
    std::vector<std::shared_ptr<T>> GetDevicesByCompatibility(const std::string& compatibility);
};


class MAX14830_Uart : public IDevice, public IStream {
public:
    bool IsReady() override;
    size_t ReadStream(uint8_t* buffer, size_t bufferSize) override;
};



void Test()
{
    DeviceManager deviceManager; // Not included in example, but the devices are instantiated and registered to the device manager

    auto devices = deviceManager.GetDevicesByCompatibility<IStream>("IStream");
    for (auto& device: devices)
    {   
        // Here is the problem, I want to check if the device is ready, but my device is of type IStream, not IDevice
        if (device->IsReady())
        {
            
        }
    }
}

Solution

  • Here are a few options:

    1. Add a deviceManager.CastDevice<IDevice>(ptr, "IDevice"); which would be quite similar to your existing method.
    2. Make IUnknown like in COM that every interface inherits from.
    3. If you don't need external code adding new interfaces, you could use IDevice for everything and have it contain methods like GetStream() that return nullptr if the interface is not available.
    4. Keep it simple and make a big "ugly" IDevice that contains methods that not all devices will have.

    All of options 1-3 add a bit of extra code to accessing the devices. In comparison 4. is the option that is least amount of code and very simple to understand, even though it is not architecturally clean and may prove problematic if the amount of methods grows large.