Передача информации о типе в функцию вместо функции виртуального шаблона C ++ - PullRequest
4 голосов
/ 02 февраля 2011

У меня есть базовый класс, который реализует следующее:

struct Consumer
{
    template <typename T>
    void callback(T msg) { /*null implementation */ }
};

У меня есть класс, реализующий это:

struct Client : public Consumer
{
     void callback(Msg1 msg);
     void callback(Msg2 msg);
     void callback(Msg3 msg);
};

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

Есть мысли, как я могу получить производный класс для вызова? Сейчас мне нужно реализовать все перегруженные функции в Consumer и пометить их как виртуальные.

Cheers, Graeme

Ответы [ 3 ]

3 голосов
/ 02 февраля 2011

Если вы действительно не хотите использовать виртуальные функции (на самом деле это идеальный вариант их использования, но я не знаю о ваших классах сообщений), вы можете использовать CRTP :

template <typename U>
struct Consumer
{
    template <typename T>
    void callback(T msg)
    { static_cast<U*>(this)->callback(msg); }
};


struct Client : Consumer<Client>
{
     void callback(Msg1 msg);
     void callback(Msg2 msg);
     void callback(Msg3 msg);
};

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

Есть ли причина, по которой классы Msg не полиморфны и не используются стандартные виртуальные функции (кроме "Я должен переписать весь код и не могу")?

EDIT Если вы беспокоитесь о классах сообщений, почему бы не использовать что-то подобное, если предположить, что классы сообщений реализуют функцию-член DoSomething: (этот метод известен как Введите Erasure )

struct AnyMsg
{
    template <typename Msg>
    AnyMsg(Msg x) : impl(newImpl(x)) {}

    void DoSomething() { impl->DoSomething(); }

private:
    struct Impl
    {
        virtual ~Impl() {}
        virtual void DoSomething() = 0;
    };

    // Probably better is std::unique_ptr if you have
    // C++0x. Or `boost::scoped_ptr`, but you have to
    // provide copy constructors yourself.
    boost::shared_ptr<Impl> impl;

    template <typename Msg>
    Impl* newImpl(Msg m)
    {
        class C : public Impl
        {
            void DoSomething() { x.DoSomething(); }
            Msg x;

        public:
            C(Msg x) : x(x) {}
        };

        return new C(m);
    }
};

Вы можете настроить поведение newImpl, чтобы получить то, что вы хотите (например, действия по умолчанию, если в классе сообщений нет функции-члена DoSomething, специализациядля некоторых классов сообщений или чего-либо еще).Таким образом, вы реализуете Msg классы, как если бы вы использовали свое шаблонное решение, и у вас есть уникальный фасад, который вы можете передать виртуальным функциям в ваших клиентских классах.

Если классы Message собираютсябыть очень разными, и клиентские классы могут по-разному реагировать на них, и у вас будет много классов сообщений, это начинает пахнуть.Или, возможно, у вас есть кандидатура на уродливый и страшный шаблон Visitor .

1 голос
/ 02 февраля 2011

Это всегда сложная ситуация, чтобы сделать ее полностью расширяемой, как это обычно бывает с шаблоном Visitor.

В конечном итоге вам потребуется до V * T реализаций, где V - это число "посетителей" иT - количество посещаемых типов, и, вероятно, в конечном итоге придется использовать комбинацию шаблона посетителя и фабрики классов.

Посетители здесь будут использовать фабрику классов ваших потребителей для типов сообщений.

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

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

Вы можете иметь один обработчик на тип для каждого класса потребителя.

1 голос
/ 02 февраля 2011

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

Конечно, вы могли бы альтернативно использовать некоторые операторы switch для ручного получения конкретного типа, но это не элегантно и не эффективно (и вам пришлось бы жестко кодировать все возможные типы клиентов ...)

EDIT

Лично я бы реализовал некоторый базовый класс сообщений, содержащий код типа, и реализовал бы оператор switch в клиентском классе для обработки различных типов сообщений, таких как:

struct MsgBase {
   int type;
};

struct Consumer {
   virtual void callback(MsgBase msg) { };
};

struct Client : public Consumer {
    void callback(MsgBase msg) {
        switch (msg.type) {
        case MSGTYPE1:
           callback((Msg1)msg);
           break;
        case MSGTYPE2:
           callback((Msg2)msg);
           break;
        // ...
        }
   }
   void callback(Msg1 msg) { /* ... */ }
   void callback(Msg2 msg) { /* ... */ }
};

Вы также можете сделать MsgBase полиморфным (например, виртуальный деструктор) и использовать typeid для дифференциации (более элегантно, но немного менее эффективно ...)

struct Client : public Consumer {
    void callback(MsgBase* msg) {
        if (typeid(*msg) == typeof(Msg1))
           callback(static_cast<Msg1*>(msg));
        else if (typeid(*msg) == typeof(Msg2))
           callback(static_cast<Msg2*>(msg));
    }
    // ...
 };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...