开发者

calling a function from a set of overloads depending on the dynamic type of an object

开发者 https://www.devze.com 2022-12-30 17:40 出处:网络
I feel like the answer to this question is really simple, but I really am having trouble finding it. So here goes:

I feel like the answer to this question is really simple, but I really am having trouble finding it. So here goes:

Suppose you have the following classes:

class Base;
class Child : public Base;

class Displayer
{
public:
    Displayer(Base* element);
    Displayer(Child* element);
}

Additionally, I have a Base* object which might point to either an instance of the class Base or an instance of the class Child.

Now I want to create a Displayer based on the element pointed to by object, however, I want to pick the right version of the constructor. As I currently have it, this would accomplish just that (I am being a bit fuzzy with my C++ here, but I think this the clearest way)

object->createDisplayer();

virtual void Base::createDisplayer()
{
     new Displayer(this);
}

virtual void Child::createDisplayer()
{
     new Displayer(this);
}

This works, however, there is a problem with this:

Base and Child are part of the application system, while Displayer is part of the GUI system. I want to build the GUI system independently of the Application system, so that it is easy to replace the GUI. This means that Base and Child should not know about Displayer. However, I do not know how I can achieve this without letting the Application classes know about the GUI.

Am I missing something very obvious or am I trying something that is not possible?

Edit: I missed a part of the problem in my original question. This is all happening quite deep in the GUI code, providing functionality that is unique to this one GUI. This means that I want the Base and Child classes not to know abou开发者_如何学Pythont the call at all - not just hide from them to what the call is


It seems a classic scenario for double dispatch. The only way to avoid the double dispatch is switching over types (if( typeid(*object) == typeid(base) ) ...) which you should avoid.

What you can do is to make the callback mechanism generic, so that the application doesn't have to know of the GUI:

class app_callback {
  public:
    // sprinkle const where appropriate...
    virtual void call(base&)    = 0;
    virtual void call(derived&) = 0;
};

class Base {
  public:
    virtual void call_me_back(app_callback& cb) {cb.call(*this);}
};
class Child : public Base {
  public:
    virtual void call_me_back(app_callback& cb) {cb.call(*this);}
};

You could then use this machinery like this:

class display_callback : public app_callback {
  public:
    // sprinkle const where appropriate...
    virtual void call(base&    obj) { displayer = new Displayer(obj); }
    virtual void call(derived& obj) { displayer = new Displayer(obj); }

    Displayer* displayer;
};

Displayer* create_displayer(Base& obj)
{
  display_callback dcb;
  obj.call_me_back(dcb);
  return dcb.displayer;
}

You will have to have one app_callback::call() function for each class in the hierarchy and you will have to add one to each callback every time you add a class to the hierarchy.
Since in your case calling with just a base& is possible, too, the compiler won't throw an error when you forget to overload one of these functions in a callback class. It will simply call the one taking a base&. That's bad.

If you want, you could move the identical code of call_me_back() for each class into a privately inherited class template using the CRTP. But if you just have half a dozen classes it doesn't really add all that much clarity and it requires readers to understand the CRTP.


Have the application set a factory interface on the system code. Here's a hacked up way to do this. Obviously, apply this changes to your own preferences and coding standards. In some places, I'm inlining the functions in the class declaration - only for brevity.

// PLATFORM CODE
// platformcode.h - BEGIN
class IDisplayer;
class IDisplayFactory
{
     virtual IDisplayer* CreateDisplayer(Base* pBase) = 0;
     virtual IDisplayer* CreateDisplayer(Child* pBase) = 0;
};

namespace SystemDisplayerFactory
{
    static IDisplayFactory* s_pFactory;
    SetFactory(IDisplayFactory* pFactory)
    {
        s_pFactory = pFactory;
    }

    IDisplayFactory* GetFactory()
    {
        return s_pFactory;
    }
};
// platformcode.h - end

// Base.cpp and Child.cpp implement the "CreateDisplayer" methods as follows

void Base::CreateDisplayer()
{
  IDisplayer* pDisplayer = SystemDisplayerFactory::GetFactory()->CreateDisplayer(this);
}


void Child::CreateDisplayer()
{
  IDisplayer* pDisplayer = SystemDisplayerFactory::GetFactory()->CreateDisplayer(this);
}

// In your application code, do this:

#include "platformcode.h"

    class CDiplayerFactory : public IDisplayerFactory
    {
        IDisplayer* CreateDisplayer(Base* pBase)
        {
             return new Displayer(pBase);
        }

        IDisplayer* CreateDisplayer(Child* pChild)
        {
            return new Displayer(pChild);
        }
    }

Then somewhere early in app initialization (main or WinMain), say the following:

CDisplayerFactory* pFactory = new CDisplayerFactory();
SystemDisplayFactory::SetFactory(pFactory);

This will keep your platform code from having to know the messy details of what a "displayer" is, and you can implement mock versions of IDisplayer later to test Base and Child independently of the rendering system.

Also, IDisplayer (methods not shown) becomes an interface declaration exposed by the platform code. Your implementation of "Displayer" is a class (in your app code) that inherits from IDisplayer.

0

精彩评论

暂无评论...
验证码 换一张
取 消