0

In our system, we have

  • multiple deviceTypes
  • each deviceType can have a different configuration type
  • each deviceType will be a library of its own

I'm in a situation where I am forced to use dynamic_cast. I'm wondering if there is a better way to design this ?

what I have is:

// in common code
class Config {public: virtual ~Config(){} }; 

    class Device {
     protected:
        Config* devConfig;
    protected:
        virtual void createDevConfig() = 0;
    public:
        virtual void display() = 0;
    };

    // Device specific Code
    class A0Device : public Device {
    protected:
        virtual void createDevConfig() { this->devConfig = new A0Config(); }
    public:
        A0Device() { this->createDevConfig(); }

        virtual void display(){
        A0Config* config = dynamic_cast<A0Config*>(this->devConfig);

        if(!config) std::cout << "Null object\n";

        }
    };

    class A0Config : public Config {};

    int main() {
        Device* dev = new A0Device();
        dev->display();
        return 0;
    }

Essentially A0Device has its own config type: A0Config, which is composed of other members. A0Device has devConfig defined as Config* in base class. In A0Device::display() - I need to access the devConfig object (as A0Config type). The virtual createDevConfig() ensures the config object will always be of type A0Config in A0Device => Is it safe to use dynamic_cast here ? Is there a better way of designing this ?

brainydexter
  • 19,826
  • 28
  • 77
  • 115
  • Why does A0Device need a base class? – Neil Kirk Aug 26 '15 at 01:57
  • If you haven't heard of [double dispatch](https://en.wikipedia.org/wiki/Double_dispatch#Double_dispatch_in_C.2B.2B), take a look. That's another possible solution. – R Sahu Aug 26 '15 at 02:14
  • You could move the `devConfig` field into `A0Device`, then change the type to `A0Config*` since it's now specific to `A0Device`s only. – user253751 Aug 26 '15 at 02:37
  • @NeilKirk SDK functions entirely on device and the knowledge of A0Device and other deviceTypes are completely abstracted in their own libraries – brainydexter Aug 26 '15 at 04:46
  • Device should call a virtual function on Config. Or Config pointer should be moved to A0Device. You could make derived device a template with the config as the parameter. – Neil Kirk Aug 27 '15 at 01:17

3 Answers3

2

If you can support the extra (minimal in general-purpose systems, enormous in real-time/embedded systems) runtime overhead, you can use dynamic_cast<> safely.

However, you must be aware of this "small" detail. Given the expression dynamic_cast<T*>(expr), whenever the dynamic type of *expr is neither T or a subclass of T, the expression evalutates to the null pointer, nullptr. And, of course, dereferencing nullptr invokes undefined behaviour, and will cause a crash on most platforms.

However, it's probably not worth it checking for nullptr if, ando only if, you know your code shall crash on such a situation.

Because C++ is a bowl with both soup and ice cream, potatoes and coffee, and of course, each idea that came through Stroustrup's/the C++ commitee's ears/mind, there are several alternatives:

  • A downcasting static_cast<T*>(expr), although this has the same issues as casting something to something else that it isn't.
  • For C people who found it "necessary", reinterpret_cast<T*>(expr) is not necessary in this but it's there.
  • If you both think that something is engineeringly-perfect if it works, and you know all the types that the object may have at runtime in a single place, you can use a enum for the type with union for holding the data with placement new with explicit destructor calls supercombo.
  • If data stuff is involved in the problem, you can have something like virtual Config &getConfig() = 0 in the Base, and something like ABC123Config config; Config &getConfig { return this->config; } in the Derived.
  • For crazyies like me only: Use a C-style/constructor-style cast.

Hope this helps!

3442
  • 8,248
  • 2
  • 19
  • 41
  • @brainydexter: Do you mean the *if that stuff is involved...* point? – 3442 Aug 26 '15 at 21:54
  • Yes : `virtual Config &getConfig() = 0 in the Base, ` – brainydexter Aug 26 '15 at 21:55
  • Also, in my question, the dynamic casting will not happen frequently, init time perhaps - so I don't think runtime expense will be a problem. As a design, do you think there is a problem here ? – brainydexter Aug 26 '15 at 22:00
  • 1
    The expression `virtual Config &getConfig() = 0` declares a [pure virtual function](http://www.programmerinterview.com/index.php/c-cplusplus/pure-virtual-function/) named `getConfig` that returns a `Config&` (and thus, the return value can reference a given `Derived` object as I said you in another comment). The `virtual` specifier tells the compiler that the dynamic type of the object shall be queried whenever calling the function through a `Base&` or `Base*`. By default, a base class defines a function to be called if the real object's type doesn't overload the function. (To be continued...) – 3442 Aug 26 '15 at 22:01
  • @brainydexter: Sorry, I had to cut the commnet in two due to it's length. **Continuation**: The `= 0` specifier (called the *pure specifier*), marks the virtual function as *pure*. A pure virtual function does *not* have a default to call if the function isn't overloaded. Thus, a class containing a pure virtual function cannot be instantiated (it would be catastrophic!), and is said to be *abstract*. In the inheritance tree, all subclasses of an abstract `Base` will be implicitly abstract until all the pure virtual functions get fully implemented by a given subclass. – 3442 Aug 26 '15 at 22:06
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88049/discussion-between-brainydexter-and-kemyland). – brainydexter Aug 26 '15 at 22:07
1

You could have a pure virtual function in the base which returns a Config pointer or reference (which I would prefer unless you need a pointer), then have the storage in the derived classes. This question/answers cover the differences between pointers and references: When to use references vs. pointers

The advantage of this design is that anything in the base that needs a Config can use getConfig, while anything that needs to use the derived class can without casting. Plus you don't need to call new and delete.

class Device {
   protected:
      virtual Config& getConfig() = 0;
   ...
};

class A0Device {
public:
   ...
   Config& getConfig() {return config;}
   ...
private:
   A0Config config;
};
Community
  • 1
  • 1
Dominique McDonnell
  • 2,510
  • 16
  • 25
  • When A0Device getconfig is called, you will get a config object and not A0Config type -- correct? – brainydexter Aug 26 '15 at 07:46
  • @brainydexter: Both pointers and references to any `Base` may actually point to/reference a given `Derived` object. – 3442 Aug 26 '15 at 21:56
  • ok but what does a reference buy me here instead of using a pointer ? This config will contain a lot of other structs which will point to HUGE arrays. Hence, I was leaning towards using pointers – brainydexter Aug 26 '15 at 21:58
  • 1
    @brainydexter: A reference to `T` is basically a constant pointer to `T` which implicitly dereferences the object whenever used. For the **huge** data structures you described, I'll prefer pointers all day long. You may read the advantages of references vs pointers and viceversa [here](http://stackoverflow.com/questions/7058339/when-to-use-references-vs-pointers). – 3442 Aug 26 '15 at 22:11
  • 1
    @brainydexter, I prefer references to objects rather than pointers if you aren't dynamically selecting an object as it is easier to read (no messy dereferences when accessing), makes sure that you can't make a mistake like incrementing the pointer (unless you intentionally do something like take the address, but you can't prevent people from intentionally misusing things), and gives more information than a pointer ('This is the thing you want' rather than 'This is one of potentially many things') – Dominique McDonnell Aug 26 '15 at 23:14
  • 1
    @brainydexter, you can think of references being const pointers, or pointers that you can't change (T* const). Don't confuse that with pointers to const objects where you can change what the pointer points to, but not the object pointed to (const T*). Any benefits that you get from pointers you also get with references. Except for being able to select an arbitrary object. – Dominique McDonnell Aug 26 '15 at 23:19
  • got it. Thanks @DominicMcDonnell – brainydexter Aug 26 '15 at 23:23
  • Thanks @KemyLand. I'll use that link in my answer. – Dominique McDonnell Aug 26 '15 at 23:26
  • @DominicMcDonnell: No problem :). – 3442 Aug 27 '15 at 00:00
  • @brainydexter, I just added to my answer to explain why I would solve your problem this way, if you hadn't figured it out yourself. – Dominique McDonnell Aug 27 '15 at 00:06
  • Thanks @DominicMcDonnell : Yes, I see the benefit. I will most likely go this way, but I will still need to use dynamic_cast. In my codebase, Device has blocks (as member pointers) and the blocks will not have a reference to device object. within a block, I get reference to device::config like this: `DeviceMgr::instance()->getDevice(deviceId)->getConfig()` => depending on deviceType the correct configType will be returned – brainydexter Aug 27 '15 at 00:12
0

[Not sure, if I'm missing something here, but it more or less sounds like a design-smell]

Why can't A0Config aggregate Config?

Quite often, hitting upon dynamic_cast is a clue that something is not right in terms of establishing IS-A relationship. In this case A0Configprovides some functionality (getA()) which doesn't fit into its base class Config, perhaps it is violating LSP-Liskov Substitution Principle thereby violating strong IS-A.

Nevertheless, If a class relationship can be expressed in more than one way, use the weakest relationship that's practical – Herb Sutter

... and Sean Parent goes to this extent. Inheritance is the base class of evil

Arun
  • 2,087
  • 2
  • 20
  • 33