вызов функции из набора перегрузок в зависимости от динамического типа объекта - PullRequest
0 голосов
/ 17 мая 2010

Мне кажется, что ответ на этот вопрос очень прост, но мне действительно трудно его найти.Итак, вот так:

Предположим, у вас есть следующие классы:

class Base;
class Child : public Base;

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

Кроме того, у меня есть Base* object, который может указывать либо на экземпляр класса Base, либо на экземпляркласса Child.

Теперь я хочу создать Displayer на основе элемента, на который указывает object, однако я хочу выбрать правильную версию конструктора.Как у меня сейчас есть, это будет выполнено именно это (я немного размыта с моим C ++ здесь, но я думаю, что это самый ясный способ)

object->createDisplayer();

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

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

Это работает, однако, есть проблема сэто:

Base и Child являются частью системы приложений, в то время как Displayer является частью системы GUI.Я хочу построить систему с графическим интерфейсом независимо от системы приложений, чтобы ее можно было легко заменить.Это означает, что Base и Child не должны знать о Displayer.Однако я не знаю, как этого добиться, не сообщая классам приложения о графическом интерфейсе.

Я что-то упускаю из виду или я пытаюсь сделать что-то невозможное?

РедактироватьЯ пропустил часть проблемы в моем первоначальном вопросе.Все это происходит довольно глубоко в коде GUI, обеспечивая функциональность, уникальную для этого GUI.Это означает, что я хочу, чтобы классы Base и Child вообще не знали о вызове, а не просто скрывали от них, что такое вызов

Ответы [ 2 ]

3 голосов
/ 17 мая 2010

Кажется, классический сценарий для двойной отправки. Единственный способ избежать двойной отправки - переключение типов (if( typeid(*object) == typeid(base) ) ...), которых следует избегать.

Что вы можете сделать, это сделать механизм обратного вызова универсальным, чтобы приложение не знало 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);}
};

Вы можете использовать эту технику так:

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;
}

У вас должна быть одна app_callback::call() функция для каждого класса в иерархии, и вам нужно будет добавлять ее к каждому обратному вызову при каждом добавлении класса в иерархию.
Поскольку в вашем случае возможен вызов только с base&, компилятор не выдаст ошибку, если вы забудете перегрузить одну из этих функций в классе обратного вызова. Он просто назовет тот, кто принимает base&. Это плохо.

Если вы хотите, вы можете переместить идентичный код call_me_back() для каждого класса в закрытый частный шаблон класса, используя CRTP . Но если у вас есть только полдюжины классов, это не добавляет особой ясности и требует от читателей понимания CRTP.

0 голосов
/ 17 мая 2010

Попросите приложение установить заводской интерфейс для системного кода. Вот взломанный способ сделать это. Очевидно, применить эти изменения к вашим собственным предпочтениям и стандартам кодирования. В некоторых местах я вставляю функции в объявление класса - только для краткости.

// 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 и Child.cpp реализуют методы «CreateDisplayer» следующим образом

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


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

// В коде приложения сделайте это:

#include "platformcode.h"

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

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

Затем где-то в начале инициализации приложения (основной или WinMain) произнесите следующее:

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

Это избавит код вашей платформы от необходимости разбираться в беспорядочных подробностях того, что такое «displayer», и вы сможете позже реализовать фиктивные версии IDisplayer для тестирования Base и Child независимо от системы рендеринга.

Кроме того, IDisplayer (методы не показаны) становится объявлением интерфейса, предоставляемым кодом платформы. Ваша реализация «Displayer» - это класс (в коде вашего приложения), который наследуется от IDisplayer.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...