2010-07-15 2 views
17

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

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

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

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

ответ

0

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

+0

Но технически я не буду создавать экземпляр IFoo, Я только хочу создать экземпляр Foo1 (или 2) (на который указывает указатель IFoo), и я предположил, что соответствующая функция может быть найдена путем динамического ограничения? – bishboshbash

+0

@bishboshbash: для функции, которую можно найти с помощью динамической привязки, должны быть известны все типы аргументов; иначе было бы непонятно, что искать, если вы использовали перегруженные функции. – liori

+0

Думаю, я понимаю. Если я создал экземпляр Foo1 и вызвал Foo1.functionB (..), он будет работать нормально, поскольку шаблон был создан. Могу ли я создать абстрактный класс, чтобы указать на объекты типа Foo1 или Foo2 на множественное наследование и, следовательно, избежать проблемы динамического связывания, пытающегося пройти через незакрепленный шаблон? – bishboshbash

4

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

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

Это означало бы, что я не мог бы создавать указатели типа IFoo и указывать на экземпляры Foo1 или Foo2. – bishboshbash

+2

Это правильно, и это фундаментальная проблема. Тип аргумента, который принимает 'functionB', зависит от типа экземпляра шаблона и ** должен быть известен во время компиляции. –

+0

@bishboshbash Ваше требование плохо сформировано в первую очередь. Foo1 и Foo2 имеют разные базовые классы (IFoo и IFoo - два разных типа). – h9uest

4

Поскольку тип аргумента functionB должны быть известны заранее, у вас есть только один выбор: сделать это тип, который может содержать все возможные аргументы. Это иногда называют «верхним типом», а библиотеки boost имеют тип 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 в double. Если это не касается конверсий, потому что все равно невозможно воспроизвести их всех. Таким образом, существует несколько ограничений, но при необходимости можно найти обходные пути.

+1

Чтобы использовать этот ответ, вызывающий ('main()') должен знать фактический подтип экземпляра 'IFoo' (' if_int' и 'if_double'), чтобы он мог передавать правильные аргументы. Если он ошибается, возникает исключение во время выполнения! Итак, почему бы не позвонить просто с помощью 'dynamic_cast'? – Karmastan

+1

Если у меня есть вызывающий пользователь, который использует dynamic_cast, то мне нужно, чтобы он знал о Foo , Foo . Но приятная вещь об абстрактном интерфейсе заключается в том, что мне не нужно никому рассказывать, как фактически реализован интерфейс. Это может быть частный, сторонний тип из общей библиотеки или локального типа в теле функции. Кроме того, это только ограничение any_cast boost, которое оно выбрасывает за неправильный тип аргумента. Вы можете расширить свои функциональные возможности, чтобы, например, делать правильные преобразования для внутренних типов. Однако нельзя сделать вывод ** всех ** действительных конверсий. –

9

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

Самое главное: virtual и template не очень хорошо перемешать.

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

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

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

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

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

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

+0

У меня есть классы Foo1 и Foo2. Оба они содержат аналогичные структуры данных: один содержит ints и один содержит рациональные числа (пары ints). Они имеют много методов, которые реализуются идентично (соответствуют int/pair int), которые взаимодействуют с другими (int/pair int) самими типами данных. Но у них есть несколько методов, которые реализуются по-разному. Первоначально я реализовал все это с наследованием, но тогда структуры данных определены в подклассах, поэтому я не могу писать функции-члены, которые действуют на них в базовом классе, что приводит к большому количеству дублирования кода. – bishboshbash

+0

У меня были те же проблемы много раз и с классами, содержащими множество методов и свойств в их интерфейсе (10+). Я сделал это для создания интерфейсов для каждого экземпляра и сокращения количества методов с использованием закрытий, например. 'ErrorCode ScalarParameterSet (ParamType t, float ParamValue)' для объединения всех сеттеров. Если вы хотите, я мог бы привести пример этого –

Смежные вопросы