У меня есть следующая проблема дизайна C ++, и я был бы очень признателен за любое предложение / решение.
Обратите внимание, что мой опыт работы не в области компьютерных наук, поэтому я могу упустить какое-то очевидное решение.
Способ, которым я обычно разделяю ключевые компоненты в коде, заключается в определении интерфейсов с помощью абстрактных классов и чисто виртуальных функций.
Example1:
class B
{
public:
virtual double f( double x ) = 0;
};
class D1 : public B
{
public:
double f( double x ) const
{
return 0.0;
}
};
class D2 : public B
{
public:
double f( double x ) const
{
return 1.0;
}
};
Таким образом, я могу красиво отделить интерфейс от реализации.
Этот подход также довольно быстр (и то, над чем я работаю, это числовая библиотека, это важно: P).
Теперь проблема, с которой я сталкиваюсь, заключается в следующем.
У меня есть набор «функциональных возможностей», которые можно суммировать с помощью функций (определенных ниже) f (), g () и h ()
Обратите внимание, что все эти функции обычно отличаются аргументами и типами возвращаемых данных.
Предположим, у меня есть некоторый код, который ожидает указатель на объект, который реализует функции f () и g ().
Я хотел бы иметь возможность передавать что-то, что имеет «более или равные» функциональные возможности, например что-то, что поддерживает f (), g () и h ().
Чтобы лучше объяснить себя, приведу код.
Обратите внимание, что вместо множественного наследования я мог бы использовать подход «вложенного наследования», как в boost :: operator. Дело в том, что у меня никогда не будет случая, когда f () будет таким же, как g (). Все функции разные.
Проблема в том, что для этой работы мне нужно использовать reinterpret_cast, как в примере ниже (так что это не совсем решение):
Пример2:
class F {
public:
virtual double f( double x ) = 0;
};
class G {
public:
virtual double g( double x ) = 0;
};
class H {
public:
virtual double h( double x ) = 0;
};
class N {};
template<class T1, class T2=N, class T3=N>
class Feature : public T1 , public T2 , public T3
{
};
template<class T1, class T2>
class Feature<T1,T2,N> : public T1, public T2
{
};
template<class T1>
class Feature<T1,N,N> : public T1
{
};
//Supp for Supports/Implements
class SuppFandG : public Feature<F,G>
{
public:
double f( double x ) { return 0.0; }
double g( double x ) { return 1.0; }
};
class SuppFandH : public Feature<F,H>
{
public:
double f( double x ) { return 0.0; }
double h( double x ) { return 1.0; }
};
class SuppFandGandH : public Feature<F,G,H>
{
public:
double f( double x ) { return 0.0; }
double g( double x ) { return 1.0; }
double h( double x ) { return 2.0; }
};
int main()
{
Feature<F,G>* featureFandGPtr;
Feature<F,H>* featureFandHPtr;
Feature<H,F>* featureHandFPtr;
Feature<F,G,H>* featureFandGandHPtr;
SuppFandGandH suppFandGandH;
featureFandGandHPtr = &suppFandGandH;
//featureFandGPtr = featureFandGandHPtr; //Illegal. static_cast illegal too.
//the reason to do this is that I would like to pass a pointer to an object
//of type Feature<F,G,H> to a function (or constructor) that expects a pointer to Feature<F,G>
featureFandGPtr = reinterpret_cast< Feature<F,G>* >( featureFandGandHPtr );
featureFandHPtr = reinterpret_cast< Feature<F,H>* >( featureFandGandHPtr );
featureHandFPtr = reinterpret_cast< Feature<H,F>* >( featureFandGandHPtr );
featureFandGPtr->f( 1.0 );
featureFandGandHPtr->h( 1.0 );
}
Или я могу попытаться построить иерархию наследования, но изменив определение Feature, но в следующем примере происходит сбой профессионального компилятора Visual Studio 2008, поэтому я не могу его протестировать.
Пример 3:
//This will not work, Visual studio 2008 professional crash.
template<class T1, class T2=N, class T3=N>
class Feature : public Feature<T1,T2> , public Feature<T1,T3> , public Feature<T2,T3>
{
};
template<class T1, class T2>
class Feature<T1,T2,N> : public Feature<T1>, public Feature<T2>
{
};
template<class T1>
class Feature<T1,N,N> : public T1
{
};
При таком подходе у меня все еще есть проблемы
1) Функция логически эквивалентна (для чего я хочу достичь) функции, но их типы разные.
Однако это может быть решено с помощью какого-то необычного метапрограммирования с использованием библиотеки повышения MPL (всегда «сортируйте» типы), поэтому для простоты предположим, что это не проблема.
2) Проблема нескольких баз, и я хочу избежать виртуального наследования через виртуальные базы (снижение производительности).
Это, вероятно, решаемо с помощью директив внутри специализаций Feature.
Тем не менее, я не уверен на 100%, что смогу сделать эту работу, и она не будет хорошо масштабироваться для большого количества функций.
Фактически количество элементов, составляющих иерархию, определяется биномиальным коэффициентом, почти факториальным:
F -> F (1)
F, G -> FG, F, G (3)
F, G, H -> FGH, FG, GH, FH, F, G, H (7)
Я хотел бы знать, есть ли решение проблемы проектирования, которая включает следующие условия:
1) Код должен иметь производительность во время выполнения, эквивалентную примеру 1.
2) Я хочу иметь возможность легко указывать некоторый набор функций и иметь возможность «передавать» любые указатели на объекты, которые имеют эту (и обычно дополнительную) функциональность.
3) Я хочу, чтобы код, зависящий от функций f () и g (), не требовал перекомпиляции всякий раз, когда я рассматриваю новую функцию h () где-то еще.
4) Я не хочу шаблонировать все, что хочет использовать такие функции (почти весь код). Должно быть какое-то «разделение», см. Пункт 3.
Глядя в (числовые) библиотеки, я обычно нашел два подхода:
1) Определите огромный абстрактный базовый класс B, который имеет f (), g (), h (), ......
Проблемы: всякий раз, когда я хочу добавить новую функцию z (), необходимо изменить B, все необходимо перекомпилировать (даже если этот код вообще не заботится о z ()), все существующие реализации D1, D2 , ... of B необходимо изменить (обычно, если они выбрасывают исключение для z (), для новой реализации, которая поддерживает z ()).Решение постепенного увеличения B, когда мне нужно добавить функции, не подходит для рассматриваемой проблемы, так как функции f () и g () действительно «так же важны», как h () и i (), и "более простой", чем другие.
2) Разделите все функции и используйте один указатель для каждой функции.
Тем не менее, это является обременительным для пользователя (в большинстве ситуаций необходимо переносить 4 или более указателей), и для рассматриваемой проблемы этот подход не является оптимальным (здесь на самом деле это 1 объект, который может или не может что-то делать, фактически вызов функции f () изменит результат, полученный с помощью функции g () и наоборот).
Заранее благодарю за помощь.
КрАО.