OOG. Сложный вопрос. Хорошо, во-первых, я попытаюсь объяснить , почему шаблонный подход, который вы здесь используете, вызывает проблемы; и тогда я попытаюсь предложить альтернативное решение.
Здесь идет.
Шаблоны являются своего рода следующей эволюцией макросов . У них больше безопасности типов, но у них все еще есть несколько ограничений, которые становятся болезненно очевидными, когда вы пытаетесь связать объекты шаблона. Это становится грязным. Ключевым моментом здесь является то, что когда вы объявляете класс:
template< class Obj > class IDependency
Ничто на самом деле не создано или не определено. Здесь не определен класс (пока), в частности, нет (и никогда не будет) фактического, пригодного к использованию класса с именем IDependency
.
Когда вы пытаетесь использовать этот класс шаблона, затем компилятор создает шаблон, генерируя фактический определенный класс (с некоторым определенным искажением имени) происходит под одеялом). То есть когда вы говорите IDependency< const wchar_t* >
, компилятор сгенерирует определение класса для const wchar_t*
аромата этого шаблона, и у вас будет класс с именем что-то вроде IDependency_const_wchart_p
за кадром.
Это может звучать как утомительная низкоуровневая детализация, но здесь это имеет значение по очень важной причине: IDependency<const wchar_t*>
и IDependency<int>
не имеют абсолютно ничего общего . Они НЕ относятся к одному классу, и у них НЕТ общего базового типа.
Это создает проблему, если вы хотите обработать универсальный IDependency
. Это означает, что вы ДОЛЖНЫ использовать конкретную его реализацию или создавать общие шаблоны-методы - но это только откладывает неизбежное - чтобы на самом деле использовать ваши шаблоны-методы, вы должны использовать конкретную реализацию шаблон.
Это довольно долго, но это означает, что , если вы хотите динамически привести к IDependency, вы не можете . Вы можете только динамически привести к созданию экземпляра IDependency.
Например: это законно:
IDependent< const wchar_t* >* dependent =
dynamic_cast< IDependent< const wchar_t* >* >( test_case );
if (dependent) {
IDependency< const wchar_t* >* dep =
dynamic_cast< IDependency< const wchar_t* >* >( dependency );
}
else
{
IDependent< const int >* dependent =
dynamic_cast<IDependent<const int>* (test_case);
if (dependent) {
...
}
}
Очевидно, это не идеально. Вы можете немного убрать это, потянув его в шаблонный метод (например, try_to_attach_dependency<T>
, а затем несколько раз вызывая его с разными типами, пока он не преуспеет.
Другая проблема : Вы пытаетесь прикрепить кучу объектов IDependency к вашему объекту IDependent - для этого требуется, чтобы все объекты IDependency имели одинаковую специализацию шаблона (т.е. вы не можете смешивать с ). Вы можете проверить это во время выполнения (поскольку мы делаем динамическое приведение) и либо игнорировать другие, либо выдать ошибку, если объекты IDependency не являются однородными. Но вы не можете применить это во время компиляции. Это опасность dynamic_cast.
Итак, что вы можете с этим поделать? Я думаю, это та часть, которая вас действительно волнует.
У вас есть несколько вариантов:
Вы можете создать фактический базовый класс IDependency . Это позволит вам передавать объекты IDependency *, что на первый взгляд кажется хорошей идеей, но проблема заключается в методе Get
. Вся причина в том, что это шаблонный класс, заключается в том, что у вас может быть метод, который возвращает разные типы, в зависимости от того, какая специализация используется. Вы не можете сделать это с базовыми типами (без некоторой креативности).
Вместо метода Obj Get()
можно использовать метод void Set(ITest*)
. Таким образом, вместо того, чтобы ITest запрашивал у IDependency информацию, вы заставляете IDependency сообщать ITest информацию. Делать это таким образом - это своего рода инверсия, но она позволяет вам создать не шаблонный базовый класс для классов IDependency. Я не уверен, как вы планировали использовать Get, поэтому этот механизм разворота может работать на вас.
Альтернативно, вы можете избавиться от списка аргументов переменной . Va_args часто является плохой идеей (без безопасности), и, подняв ее до уровня вызывающего, вы можете шаблонизировать функцию и сделать следующее:
template <typename T>
void Register( ITest* test_case, const std::vector< IDependency<T>* >& dependencies )
{
IDependent<T>* dependent = static_cast< IDependent<T>* >( test_case );
for( std::vector< IDependency<T>* >::const_iterator iter = dependencies.begin();
iter != dependencies.end();
++iter)
{
dependent->SetDependency( *iter );
}
Register( test_case );
}
Затем вы можете использовать это, выполнив следующие действия:
std::vector< IDependency<const wchar_t*> > dependencies; // or whatever type you want
dependencies.push_back(&foo);
// ... more dependencies as needed
Register(&bar, dependencies);
Это прилагает немного больше усилий к вызывающей стороне (т. Е. Вы не можете просто сбросить все это в список через запятую), но это работает, и это безопаснее. Обратите внимание, что теперь мы можем использовать static_cast
, который проверяется во время компиляции.
Этот подход ограничивает вас только наличием зависимостей, основанных на одной специализации типа. Если вы хотите иметь независимые от типа зависимости, вам будет нужен не шаблонный способ хранения зависимостей.
Святая корова, это был длинный пост. Во всяком случае, я надеюсь, что это поможет. Дайте мне знать, если это работает, или если у вас есть какие-либо вопросы. Если я подумаю о чем-то еще, я добавлю это сюда.