dynamic_cast <> переменные аргументы в шаблоны - PullRequest
4 голосов
/ 22 сентября 2010

У меня есть приложение на C ++, которое выполняет контрольные примеры. Возможно, что некоторые тестовые примеры будут зависеть от результатов других тестовых случаев.

Все тестовые примеры реализуют базовый интерфейс:

/// base class for all test cases
class ITest
{
public:
    virtual void Execute() = 0;
};

Тестовые случаи, которые производят некоторый объект, который может быть полезен для других тестовых примеров, реализующих этот интерфейс:

/// implemented by test cases that provide data to other test cases
template< class Obj >
class IDependency
{
public:
    virtual Obj Get() = 0;
};

Тестовые случаи, требующие данных из других тестовых примеров, реализуют этот интерфейс:

/// implemented by test cases that require data from other test cases
template< class Obj >
class IDependent
{
public:

    void SetDependency( IDependency< Obj >* dependency )
    {
        dependency_ = dependency;
    };

protected:
    Obj GetDependency() const
    {
        return dependency_->Get();
    };

private:
    IDependency< Obj >* dependency_;
};

Два примера тестовых случаев. Один требует const wchar_t объекта; каждый производит этот объект:

/// A test case that provides a "const wchar_t*" object to other test cases
class Foo : public ITest, 
            public IDependency< const wchar_t* >
{
public:
    const wchar_t* Get() 
    { 
        if( object_.length() == 0 )
            Execute();
        return object_.c_str();
    };

    virtual void Execute()
    {
        printf( "Execute Foo\n" );
        object_ = L"Object produced by Foo";
    };

private:
    std::wstring object_;
};

/// A test case that first requires a "const wchar_t*" object
class Bar : public ITest,
            public IDependent< const wchar_t* >
{
public:

    virtual void Execute()
    {
        const wchar_t* needed_object = GetDependency();

        printf( "Execute Bar with %S\n", needed_object );
    };
};

Контрольные примеры хранятся в списке. Случаи добавляются в список путем регистрации:

/// List of test cases to execute
std::vector< ITest* > list_;

/// Register a test case to execute with the system
void Register( ITest* test_case )
{
    list_.push_back( test_case );
}

Вот моя проблема. Я хотел реализовать перегрузку функции Register (), которая также принимает зависимости. Но поскольку зависимости могут быть любого типа (не только «const wchar_t *» из этого примера), я не уверен, как с этим справиться. Ниже приведен пример более или менее того, что я ищу, но я не уверен, как заставить это работать.

/// Register a test case with dependencies with the system
void Register( ITest* test_case, ITest* dependency, ... )
{
    IDependent< ??? >* dependent = dynamic_cast< IDependent< ??? >* >( test_case );
    IDependency< ??? >* dep = dynamic_cast< IDependency< ??? >* >( dependency );

    va_list dep_list;
    for( va_start( dep_list, dependency ); 
         NULL != dep; 
         dep = dynamic_cast< IDependency< ??? >* >( va_arg( dep_list, ITest* ) ) )
    {
        dependent->SetDependency( dep );
    }
    va_end( dep_list );

    Register( test_case );
}

Пример использования:

int _tmain( int argc, _TCHAR* argv[] )
{
    /// Test case Foo
    Foo foo;

    /// Test case bar (depends on Foo)
    Bar bar;

    /// Register test case Bar with a dependency on Foo
    Register( &bar, &foo );

    /// Execute Bar. Because it depends on Foo, that will be executed first
    list_->begin()->Execute();
    return 0;
}

Ожидаемый результат:

Execute Foo
Execute Bar with Object produced by Foo

У кого-нибудь есть предложения по успешной реализации этой архитектуры? (или лучшая архитектура, которая действительно работает?)

Спасибо, PaulH

Ответы [ 2 ]

7 голосов
/ 22 сентября 2010

Я вижу два возможных решения.

static

Сделать метод Register () шаблоном.Простым решением было бы ограничить число зависимостей до некоторого разумного максимума.

template <class T, class D1>
void Register(T* test_case, IDependency<D1>* d1)
{
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
    // since we now know that T is a IDependent<D1>, a dynamic_cast would only be necessary
    // to allow virtual inheritance.
    static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
    Register(test_case);
}

template <class T, class D1, class D2>
void Register(T* test_case, IDependency<D1>* d1, IDependency<D2>* d2)
{
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
    static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
    Register(test_case, d2);
}

template <class T, class D1, class D2, class D3>
void Register(T* test_case, IDependency<D1>* d1, IDependency<D2>* d2, IDependency<D3>* d3)
{
    BOOST_STATIC_ASSERT(boost::is_base_and_derived<IDependent<D1>, T>::value);
    static_cast<IDependent<D1>*>(test_case)->SetDependency(d1);
    Register(test_case, d2, d3);
}

// ...

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

В качестве альтернативы вы можете заставить Register () возвращать прокси-класс, чтобы вы могли написать что-то вроде этого:

Register(test_case)(dep1)(dep2)(dep3) /* ... */ (depN);

Прокси-класс будет хранить указатель на контейнер и тестcase и определите оператор вызова функции, который выглядит так же, как функция Register (T * test_case, IDependency * d1) в приведенном выше примере, только без аргумента "test_case" и окончательного вызова Register (test_case) (которыйможет сделать в dtor прокси-класса).

dynamic

Если я понимаю, что вы пытаетесь сделать правильно, каждая «Зависимость» может дать только один тип результата.В этом случае вы можете изменить интерфейс IDependency следующим образом:

class IDependencyBase
{
public:
    virtual void ApplyTo(ITest* target) = 0;
};

template <class T>
class IDependency : public IDependencyBase
{
public:
    virtual void ApplyTo(ITest* target)
    {
        // cast to reference gives us an std::bad_cast if the types are not compatible,
        // which I think is a good thing here
        dynamic_cast<IDependancy<T>&>(*target).SetDependancy(this); 
    }

    virtual T Get() = 0;
};

template <class InputIterator>
void Register(ITest* test_case, InputIterator begin, InputIterator end)
{
    for (; begin != end; ++begin)
    {
        IDependancyBase* dep = *begin;
        dep->ApplyTo(test_case);
    }
    Register(test_case);
}

template <class Container>
void Register(ITest* test_case, Container deps)
{
    Register(test_case, deps.begin(), deps.end());
}

Теперь вам может показаться заманчивым снова реализовать решение varargs, что-то вроде этого (продолжение второго примера):

void Register(ITest* test_case, ...)
{
    va_list dep_list;
    va_start(dep_list, test_case);
    while(IDependencyBase* dep = va_arg(dep_list, ITest*))
        dep->ApplyTo(test_case);

    va_end(dep_list);

    Register( test_case );
}

// and use it like

int _tmain( int argc, _TCHAR* argv[] )
{
    Foo foo;
    Bar bar;

    Register(&foo);
    Register(&bar, &foo, 0);

    list_->begin()->Execute();
    return 0;
}

Однако это не гарантированно сработает.В приведенном выше коде Foo * сохраняется в качестве аргумента varagrs и считывается обратно как IDependencyBase *.Это не гарантированно сработает, поскольку ни Foo, ни IDependencyBase не являются PODами (IIRC они оба должны быть PODами, чтобы это гарантированно работало - может быть, даже в этом случае это не гарантировано, мне придется искать это в стандарте),Это не какая-то надуманная «не гарантированная стандартом, но будет работать везде» вещь.Ввести множественное и / или виртуальное наследование, и это почти гарантированно завершится неудачей.

Поэтому общий совет при использовании C ++: не используйте функции varargs, если нет другого пути.И всегда есть другой путь.

3 голосов
/ 22 сентября 2010

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, который проверяется во время компиляции.

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

Святая корова, это был длинный пост. Во всяком случае, я надеюсь, что это поможет. Дайте мне знать, если это работает, или если у вас есть какие-либо вопросы. Если я подумаю о чем-то еще, я добавлю это сюда.

...