Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: How can you assert that the type of an object is derived from another type?  (Read 1295 times)

0 Members and 1 Guest are viewing this topic.

Critkeeper

  • Jr. Member
  • **
  • Posts: 62
    • View Profile
At runtime.

Please read before commenting, the problem is very subtle (Its not as simple as using an assert typeid )

I have made my own implementation of a scene graph and I have it so that you can attach children nodes to any other node in the graph as normal. What is special in my implementation is this:

you can attach a child node to a parent node only if the child's type is derived from the parent or is the same as the parent.

In order to understand why I have this restriction, consider what happens when a Node calls its update() in a Scene Graph. It must call its own (virtual) updateThis() and then walk through the graph and update each child, so each child must derive from type Node.

Now consider I want to create a brand new KIND of node called "Renderable". Renderable has draw() and virtual drawThis(). Renderable is a type of Node, so it can call update() and has virtual updateThis(). The problem comes from void attachChild( Node* child ). If I attach a simple Node it will compile but when I call draw() on the highest node of type Renderable, and it tries to drawThis() on a child of type Node, it will obviously crash. I have to ensure that all its children are of type Renderable, rather than just type Node if I mandate that draw() propagates to every child.

That is the dillemma. I want draw() to propagate to every child.

I'm using this pattern heavily. I have an Audible class with a play() and virtual playThis() method which does what you expect.

And I have multiple (virtual) inheritance to create classes which define objects that can be updated, can be drawn, and can be heard. For example, a background can be drawn and updated, a vessel can be drawn and updated and heard, and a stage cannot be drawn or heard but it can be updated. In order to make the stage "visible" you add a background to it by attachChild( Background* ), where Background is derived from Node* ( and happens to derive from Renderable as well ).

This scheme gives me a lot of flexibility but it is type unsafe. I have to excersise caution and due diligence to ensure that I do not try to static_cast a Node to an Audible for example. From a design / philosophical point of view I do this by mandating that every child of something that can be heard, must also be something that can be heard. Every child of something that can be seen must also be something that can be seen. Every child of something that can be updated must also be something that can be updated.

First let me say that IT WORKS if I am careful and I ensure that every child is "derived" enough. But the problem is how do I tell the compiler to warn me when it isn't? And what about run time safety? I want to compiler to tell me when I broke my own rules, so I need a way to tell the compiler what my rules are.

Its not as easy as it seems to solve the type safety problem. You would think that C++ gives you tools to check the type and make an error if the type isn't correct, but there are various barriers that prevent me from using the standard tools:

I can't use assert ( typeid( this* ) == typeid( argumentToAttachChild ) ) because the most derived type is always used, and according to the documentation these expressions will not compare equal unless the types are exactly the same-- a derived type will not compare equal to a type it derives from. Assert also doesn't give me run time safety.

I can't simply override the attachChild( Node* ) with attachChild( Renderable* ) in the class definition for Renderable because it creates ambiguity since every Renderable is a Node and technically the signatures of these functions are different so its not really an override.

I can't use the curiously recurring template pattern because it is possible to attach a Renderable to a Node.

I can't figure out how to make this more safe. It works if used correctly, and it is quite powerful, but I can't figure out how to make it safe.

Also keep in mind that this "rule" only applies to what I term "propagating" node subtypes. A propagating node subtype is one that has a operate() and virtual operateThis(). A turret isn't necessarily a vessel even though it can be attached as a child of a vessel for example. But the fact that a Vessel is renderable mandates that a Turret is renderable.

So the title to this question and the previous statement "you can attach a child node to a parent node only if the child's type is derived from the parent or is the same as the parent" isn't quite accurate. Through multiple inheritance an object is composed of an agglomeration of different types, so I want to ensure that only the "propagating" types actually propagate through the sceneGraph.
« Last Edit: February 05, 2015, 02:33:40 pm by Critkeeper »
if(CritKeeper.askQuestion()){return question.why();}

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
A few ideas to solve the technical problem:

1. If the derived type of both the parent and child is always known when you add the node, you can trigger compiler errors with the following implementation:
class Node
{
    void attach(Node* node)
    {
         m_children.push_back(node); // or whatever...
    }
};

class Renderable : public Node
{
    // shadows the base class overload: only this one is accessible when you attach something to a Renderable
    void attach(Renderable* node)
    {
         Node::attach(node);
    }
};

// etc...

Renderable* renderable;
renderable->attach(new Renderable); // ok :)
renderable->attach(new Node); // compile error :)
renderable->attach(new Playable); // compile error :)

// but...
Node* node = new Renderable;
node->attach(new Renderable); // ok :)
node->attach(new Node); // ok :(
node->attach(new Playable); // ok :(

// and...
renderable->attach(node); // compile error :(

2. If the derived type of a node is not always known when you attach it to its parent (i.e. you already have it as a Node*), then you can perform a runtime check with the dynamic_cast operator (RTTI must be enabled in project settings):

class Renderable : public Node
{
    void render()
    {
        renderThis();

        for (Node* child : children())
        {
            Renderable* renderable = dynamic_cast<Renderable*>(child);
            if (renderable)
                renderable->render();
            else
                ; // ... assert, throw, whatever ...
        }
    }
};

Now, the most important thing is that your design could be improved to avoid this kind of tricks, and be less constrained. However I don't have the time to talk on this subject, and I know a few other members that will do it better than me anyway ;)
« Last Edit: February 05, 2015, 03:04:52 pm by Laurent »
Laurent Gomila - SFML developer

Critkeeper

  • Jr. Member
  • **
  • Posts: 62
    • View Profile
Thankyou very much Laurent I didn't think of shadowing!

As for a design / philosophical point of view I'm open to any criticism! Criticism breeds better code.

EDIT:
The RTTI is something I've never seen before, but after careful inspection it looks like this is exactly what I want. Issue solved!
« Last Edit: February 05, 2015, 03:23:53 pm by Critkeeper »
if(CritKeeper.askQuestion()){return question.why();}