Создание интерфейса для абстрактного шаблона класса в C ++ - PullRequest
21 голосов
/ 15 июля 2010

У меня есть код, как показано ниже.У меня есть абстрактный шаблон класса Foo и два подкласса (Foo1 и Foo2), которые являются производными от экземпляров шаблона.Я хочу использовать в моей программе указатели, которые могут указывать на объекты типа Foo1 или Foo2, поэтому я создал интерфейс IFoo.

Моя проблема в том, что я не уверен, как включить functionB в интерфейс, так какэто зависит от реализации шаблона.Можно ли даже сделать функцию B доступной через интерфейс или я пытаюсь сделать невозможное?

Большое спасибо за помощь.

class IFoo {
    public:
        virtual functionA()=0;

};

template<class T>
class Foo : public IFoo{
    public:
        functionA(){ do something; };
        functionB(T arg){ do something; };
};

class Foo1 : public Foo<int>{
...
};

class Foo2 : public Foo<double>{
...
};

Ответы [ 5 ]

16 голосов
/ 15 июля 2010

Вы на самом деле пытаетесь невозможного.

Суть дела проста: virtual и template плохо смешиваются.

  • template о генерации кода во время компиляции. Вы можете думать об этом как о каком-то макросе с поддержкой типов + несколько уловок для метапрограммирования.
  • virtual о решении во время выполнения, и это требует некоторой работы.

virtual обычно реализуется с использованием виртуальных таблиц (представьте таблицу, в которой перечислены методы). Количество методов должно быть известно во время компиляции и определено в базовом классе.

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

А если бы это было возможно?

Ну, это просто не имело бы смысла. Что происходит, когда я звоню Foo2 с int? Это не для этого! Следовательно, это нарушает принцип, согласно которому Foo2 реализует все методы из IFoo.

Итак, было бы лучше, если бы вы указали реальную проблему, чтобы мы могли помочь вам на уровне проектирования, а не на техническом уровне:)

6 голосов
/ 15 июля 2010

Самый простой способ сделать ваш интерфейс шаблонным.

template <class T>
class IFoo {
    public:
        virtual void functionA()=0;
        virtual void functionB(T arg){ do something; };
};

template<class T>
class Foo : public IFoo<T>{
    public:
        void functionA(){ do something; };
        void functionB(T arg){ do something; };
};
4 голосов
/ 15 июля 2010

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

#include <boost/any.hpp>
#include <iostream>
using namespace boost;

class IFoo {
    public:
    virtual void functionA()=0;
    virtual void functionB(any arg)=0; //<-can hold almost everything
};

template<class T>
class Foo : public IFoo{
    public:
        void functionA(){  };
        void real_functionB(T arg)
        {
         std::cout << arg << std::endl;
        };
        // call the real functionB with the actual value in arg
        // if there is no T in arg, an exception is thrown!

        virtual void functionB(any arg)
        {
            real_functionB(any_cast<T>(arg));
        }
};

int main()
{
    Foo<int> f_int;
    IFoo &if_int=f_int;

    if_int.functionB(10);

    Foo<double> f_double;
    IFoo &if_double=f_double;
if_int.functionB(10.0);

}

К сожалению, any_cast не знает об обычных конверсиях. Например, any_cast<double>(any(123)) выдает исключение, потому что он даже не пытается преобразовать целое число 123 в двойное число. Если не заботятся о конверсиях, потому что все равно их невозможно воспроизвести. Таким образом, есть несколько ограничений, но при необходимости можно найти обходные пути.

0 голосов
/ 20 апреля 2018

Вы можете достичь чего-то сравнимого, поместив указатель IFoo * в класс и предоставив функциональность с помощью общих шаблонных функций класса-шаблона не-шаблонов:

#include <assert.h>

// interface class
class IFoo {
public:
    virtual int type() const = 0; // return an identifier for the template parameter
    virtual bool functionA() = 0;
};

// This function returns a unique identifier for each supported T
template <typename T> static int TypeT() { static_assert("not specialized yet"); }
template <> static int TypeT<bool>() { return 0; }
template <> static int TypeT<double>() { return 1; }
//template <> static int TypeT<...>() { ... }

// templated class
template <typename T> class FooT : public IFoo {
public:
    int type() const override { return TypeT<T>(); }

    bool functionA() override { return true; }

    // not in interface
    bool functionB(T arg) { return arg == T(); }
};

// function to create an instance of FooT (could also be static function in FooT)
static IFoo* CreateFooT(int type)
{
    switch (type)
    {
    case 0: return new FooT<bool>();
    case 1: return new FooT<double>();
    //case ...: return new FooT<...>();
    default: return nullptr;
    }
}


// Non-templated wrapper class
class FooWrapper {
private:
    IFoo *pFoo;
public:
    FooWrapper(int type) : pFoo(CreateFooT(type)) { assert(pFoo != nullptr); }
    ~FooWrapper() { delete pFoo; }

    bool functionA() { return pFoo->functionA(); }

    template <typename T> bool functionB(T arg)
    {
        if(pFoo->type() != TypeT<T>())
        {
            assert(pFoo->type() == TypeT<T>());
            return false;
        }
        return static_cast<typename FooT<T>*>(pFoo)->functionB(arg);
    }



    // fun stuff:
    // (const pendants omitted for readability)

    bool changeType(int type)
    {
        delete pFoo;
        pFoo = CreateFooT(type);
        return pFoo != nullptr;
    }

    IFoo* Interface() { return pFoo; }

    IFoo* operator->() { return pFoo; }

    operator IFoo&() { return *pFoo; }

    template <typename T> FooT<T> *InterfaceT()
    {
        if(pFoo->type() != TypeT<T>())
        {
            assert(pFoo->type() == TypeT<T>());
            return nullptr;
        }
        return static_cast<typename FooT<T>*>(pFoo);
    }
};

int main(int argc, char *argv[])
{
    FooWrapper w1(TypeT<bool>());
    FooWrapper w2(TypeT<double>());

    w1.functionA(); // ok
    w2.functionA(); // ok

    w1.functionB(true); // ok
    w1.functionB(0.5); // runtime error!

    w2.functionB(true); // runtime error!
    w2.functionB(0.5); // ok


    // fun stuff
    w2.changeType(TypeT<bool>()); // older changes will be lost
    w2.functionB(true); // -> now ok

    w1.Interface()->functionA();
    w1->functionA();

    IFoo &iref = w1;
    iref.functionA();

    FooT<bool> *ref = w1.InterfaceT<bool>();
    ref->functionB(true);

    return 0;
}

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

0 голосов
/ 15 июля 2010

Я не думаю, что вы можете получить то, что вы хотите.Подумайте об этом, если бы вы реализовали свое предложение: если у вас есть указатель на экземпляр IFoo и вы вызываете functionB(), какой параметр типа вы должны указать?Основная проблема в том, что Foo1::functionB и Foo2::functionB имеют разные подписи и делают разные вещи.

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