Есть ли предложения по выполнению произвольной операции с использованием заданных аргументов произвольных типов?

В основном я просто хочу выполнить произвольную операцию, используя заданные аргументы произвольных типов.

Базовым классом типа аргумента является Var, а Operation - базовым классом операции, которая будет выполняться для заданных аргументов.

У меня есть класс Evaluator, который содержит набор операторов, отображаемых с помощью opId. Оценщик выполнит операцию на основе аргумента opId, указанного в функции-члене eval (), затем функция Assessment () выполнит поиск поддерживаемого оператора, который будет принимать тип аргумента и opId.

я хочу спросить, есть ли какой-нибудь эффективный шаблон или алгоритм, который будет делать это без dynamic_cast ‹> и / или цикла по сбору операторов.

`

class Var {
public:
    bool isValidVar();
    static Var invalidVar();
}

template<typename T> class VarT : public Var {
public:
    virtual const T getValue() const;   
}

class Operator {
public:
    virtual Var evaluate(const Var& a, const Var& b) = 0;
}

template<typename T> class AddOperator : public Operator {
public:
    virtual Var evaluate(const Var& a, const Var& b)
    {                             //dynamic_cast is slow!
        const VarT<T>* varA = dynamic_cast<const VarT<T>*>(&a);
        const VarT<T>* varB = dynamic_cast<const VarT<T>*>(&b);
        if(varA && varB)          //operation supported
        {
            return VarT<T>(varA->getValue() + varA->getValue());
        }
        return Var::invalidVar(); //operation for this type is not supported
    }
}

class Evaluator {
private:
    std::map<int,std::vector<Operator>> operatorMap;
public:
    virtual Var evaluate(const Var& a, const Var& b,int opId)
    {
        std::map<int,std::vector<Operator>>::iterator it = this->operatorMap.find(opId);
        if(it != this->operatorMap.end())
        {
            for(size_t i=0 ; i<it->second.size() ; i++)
            {
                Var result = it->second.at(i).evaluate(a,b);
                if(result.isValidVar())
                {
                    return result;
                }
            }
        }
        //no operator mapped, or no operator support the type
        return Var::invalidVar();
    }
}

`


person uray    schedule 04.03.2010    source источник
comment
Уточните, похоже, у вас есть несколько наборов операций. Ваш OpId соответствует нескольким операциям. По какому критерию вы хотите использовать операцию?   -  person Björn Pollex    schedule 04.03.2010
comment
да, для каждого opId он может иметь более одного оператора, используемый оператор - это оператор, который может принимать тип данных аргументов. например, у меня есть AddOperator ‹int›, AddOperator ‹float›, AddOperator ‹Matrix›, AddOperator ‹Vec›, все эти операторы сопоставляются с одним opId (который является идентификатором для добавления), но имеют разные операторы для каждого типа аргумента   -  person uray    schedule 04.03.2010
comment
Как бы вы не зацикливались при использовании контейнера операций? Достаточно ли неявного цикла с BOOST_FOREACH?   -  person stefaanv    schedule 04.03.2010
comment
ну, не обязательно использовать контейнер, это может быть какая-то экзотическая шаблонная таблица операторов, поэтому я мог бы получить указатель на оператор напрямую, просто используя тип аргумента и тип операции. или какой-либо другой механизм или алгоритм для сопоставления аргумента и его оператора.   -  person uray    schedule 04.03.2010
comment
@user, да, но вы все равно хотите, чтобы над вашим var выполнялось несколько операций ...   -  person stefaanv    schedule 04.03.2010


Ответы (3)


если вы не хотите использовать dynamic_cast, подумайте о добавлении признаков типа в свой дизайн.

Добавлено 05/03/10: следующий пример продемонстрирует, как работают черты времени выполнения.

CommonHeader.h

#ifndef GENERIC_HEADER_INCLUDED
#define GENERIC_HEADER_INCLUDED

#include <map>
#include <vector>
#include <iostream>

// Default template
template <class T>
struct type_traits
{
    static const int typeId = 0;
    static const int getId() { return typeId; }
};

class Var 
{
public:
    virtual ~Var() {}
    virtual int     getType() const = 0;
    virtual void    print() const = 0;
};

template<typename T> 
class VarT  : public Var
{
    T value;
public:
    VarT(const T& v): value(v) {}
    virtual int     getType() const { return type_traits<T>::getId();   };
    virtual void    print() const { std::cout << value << std::endl;    };
    const T& getValue() const { return value; }
};

class Operator 
{
public:
    virtual ~Operator() {}
    virtual Var* evaluate(const Var& a, const Var& b) const = 0;
};

template<typename T> 
class AddOperator : public Operator
{
public:

    virtual Var* evaluate(const Var& a, const Var& b) const
    {   
        // Very basic condition guarding
        // Allow operation within similar type only
        // else have to create additional compatibility checker 
        // ie. AddOperator<Matrix> for Matrix & int
        // it will also requires complicated value retrieving mechanism
        // as static_cast no longer can be used due to unknown type.
        if ( (a.getType() == b.getType())                   &&
             (a.getType() == type_traits<T>::getId())       &&
             (b.getType() != type_traits<void>::getId())  )
        {
            const VarT<T>* varA = static_cast<const VarT<T>*>(&a);
            const VarT<T>* varB = static_cast<const VarT<T>*>(&b);

            return new VarT<T>(varA->getValue() + varB->getValue());
        }
        return 0;
    }
};


class Evaluator {
private:
    std::map<int, std::vector<Operator*>> operatorMap;
public:
    void registerOperator(Operator* pOperator, int iCategory)
    {
        operatorMap[iCategory].push_back( pOperator );
    }

    virtual Var* evaluate(const Var& a, const Var& b, int opId)
    {
        Var* pResult = 0;
        std::vector<Operator*>& opList = operatorMap.find(opId)->second;
        for (   std::vector<Operator*>::const_iterator opIter = opList.begin();
                opIter != opList.end();
                opIter++    )
        {
            pResult = (*opIter)->evaluate( a, b );
            if (pResult)
                break;
        }

        return pResult;
    }
};

#endif

Заголовок DataProvider

#ifdef OBJECTA_EXPORTS
#define OBJECTA_API __declspec(dllexport)
#else
#define OBJECTA_API __declspec(dllimport)
#endif

// This is the "common" header
#include "CommonHeader.h"

class CFraction 
{
public:
    CFraction(void);
    CFraction(int iNum, int iDenom);
    CFraction(const CFraction& src);

    int m_iNum;
    int m_iDenom;
};

extern "C" OBJECTA_API Operator*    createOperator();
extern "C" OBJECTA_API Var*         createVar();

Реализация DataProvider

#include "Fraction.h"

// user-type specialization
template<>
struct type_traits<CFraction>
{
    static const int typeId = 10;
    static const int getId() { return typeId; }
};

std::ostream&   operator<<(std::ostream& os, const CFraction& data)
{
    return os << "Numerator : " << data.m_iNum << " @ Denominator : " << data.m_iDenom << std::endl;
}

CFraction   operator+(const CFraction& lhs, const CFraction& rhs)
{
    CFraction   obj;
    obj.m_iNum = (lhs.m_iNum * rhs.m_iDenom) + (rhs.m_iNum * lhs.m_iDenom);
    obj.m_iDenom = lhs.m_iDenom * rhs.m_iDenom;
    return obj;
}

OBJECTA_API Operator* createOperator(void)
{
    return new AddOperator<CFraction>;
}
OBJECTA_API Var* createVar(void)
{
    return new VarT<CFraction>( CFraction(1,4) );
}

CFraction::CFraction() :
m_iNum (0),
m_iDenom (0)
{
}
CFraction::CFraction(int iNum, int iDenom) :
m_iNum (iNum),
m_iDenom (iDenom)
{
}
CFraction::CFraction(const CFraction& src) :
m_iNum (src.m_iNum),
m_iDenom (src.m_iDenom)
{
}

DataConsumer

#include "CommonHeader.h"
#include "windows.h"

// user-type specialization
template<>
struct type_traits<int>
{
    static const int typeId = 1;
    static const int getId() { return typeId; }
};

int main()
{
    Evaluator e;

    HMODULE hModuleA = LoadLibrary( "ObjectA.dll" );

    if (hModuleA)
    {
        FARPROC pnProcOp = GetProcAddress(hModuleA, "createOperator");
        FARPROC pnProcVar = GetProcAddress(hModuleA, "createVar");

        // Prepare function pointer
        typedef Operator*   (*FACTORYOP)();
        typedef Var*        (*FACTORYVAR)();

        FACTORYOP fnCreateOp = reinterpret_cast<FACTORYOP>(pnProcOp);
        FACTORYVAR fnCreateVar = reinterpret_cast<FACTORYVAR>(pnProcVar);

        // Create object
        Operator*   pOp = fnCreateOp();
        Var*        pVar = fnCreateVar();

        AddOperator<int> intOp;
        AddOperator<double> doubleOp;
        e.registerOperator( &intOp, 0 );
        e.registerOperator( &doubleOp, 0 );
        e.registerOperator( pOp, 0 );

        VarT<int> i1(10);
        VarT<double> d1(2.5);
        VarT<float> f1(1.0f);

        std::cout << "Int Obj id : " << i1.getType() << std::endl;
        std::cout << "Double Obj id : " << d1.getType() << std::endl;
        std::cout << "Float Obj id : " << f1.getType() << std::endl;
        std::cout << "Import Obj id : " << pVar->getType() << std::endl;

        Var* i_result = e.evaluate(i1, i1, 0); // result = 20
        Var* d_result = e.evaluate(d1, d1, 0); // no result
        Var* f_result = e.evaluate(f1, f1, 0); // no result
        Var* obj_result = e.evaluate(*pVar, *pVar, 0); // result depend on data provider
        Var* mixed_result1 = e.evaluate(f1, d1, 0); // no result
        Var* mixed_result2 = e.evaluate(*pVar, i1, 0); // no result

        obj_result->print();
        FreeLibrary( hModuleA );
    }
    return 0;
}
person YeenFei    schedule 04.03.2010
comment
Работают ли черты типа с наследованием? Я имею в виду, если у меня есть ссылка на объект через его базовый класс, могу ли я получить черты фактического типа? Кроме того, как бы вы разместили новые операции, добавляемые во время выполнения? - person Björn Pollex; 04.03.2010
comment
вам просто нужно добавить функцию-член, возвращающую информацию о признаках типа. Я поцарапаю код, чтобы проиллюстрировать это позже - person YeenFei; 05.03.2010
comment
Я не думаю, что с использованием признаков типа он будет работать, если оператор или тип предоставлены DLL, которая загружается во время выполнения приложениями, поскольку, используя черты типа, приложения должны включать файл заголовка, в котором находится этот шаблонный тип. время компиляции, не так ли? - person uray; 05.03.2010
comment
uray: это будет работать, так как мы получаем информацию о типе с помощью функции вместо стиля черт чистого типа (выбор компилятора). Все, что я опубликовал (минус type_traits ‹int› и type_traits ‹double›), является базовым заголовком. вы можете иметь свой настраиваемый объект (например, CMatrix) в отдельном исходном файле, если они обеспечивают необходимую специализацию для себя для компиляции, пользователю объекта в другой DLL будет все равно, и он продолжит работать как обычно. - person YeenFei; 05.03.2010
comment
ic, то для значения int typeId он должен управляться централизованно, поскольку каждый идентификатор должен быть уникальным, верно? в случае, если DLL представляет новый тип, DLL должна запрашивать уникальный typeId из приложения - person uray; 05.03.2010
comment
uray: да, если вы используете int typeId, вам нужно реализовать определенный механизм для предотвращения столкновения типов. Я обновил образец кода, чтобы показать, что он работает в сценарии DLL. - person YeenFei; 05.03.2010
comment
спасибо за коды, которые действительно помогают. если писатель DLL - это я, я буду использовать черты типа, поскольку он более элегантен и понятен, но я думаю, что моему клиенту (автору DLL) слишком сложно расширить мое приложение, если они должны написать свою специализацию по шаблону, я думаю учитывая объяснение того, как это работает, достаточно просто использовать виртуальную функцию getType () в классе Var, которая вернет идентификатор типа, который будет автоматически назначен репозиторием Var, поэтому клиенту просто нужно зарегистрировать его и не нужно знать о значениях идентификатора типа. Как ты думаешь? - person uray; 05.03.2010
comment
Я не уверен, как работает ваш репозиторий Var, но подойдет любая тщательно разработанная архитектура. - person YeenFei; 05.03.2010
comment
@uray: если вы не можете полагаться на getId (), вы можете полагаться на другой метод typeid(5).name() даст вам const char*, который гарантированно будет уникальным (хотя и не сопоставимым с одним из другого компилятора). Для gcc это дает, например, искаженное имя, поэтому, если у вас нет столкновения символов, все в порядке. - person Matthieu M.; 05.03.2010
comment
Чем это отличается от простого помещения виртуальной функции в класс, которая возвращает целое число, идентифицирующее тип? - person Billy ONeal; 02.07.2011

Если вы можете изменить тип Var, вы можете добавить идентификаторы типов для типов аргументов. Но при реализации ваших операций в какой-то момент вам всегда придется использовать dynamic_cast. Если ваши типы и операции фиксируются во время компиляции, вы можете сделать все это с помощью шаблонов, используя Boost.MPL (в частности, контейнеры).

person Björn Pollex    schedule 04.03.2010
comment
к сожалению, и оператор, и тип являются динамическими, например, DLL может вводить новый оператор, отображая его в Evaluator, или отправляя какой-то причудливый тип аргумента в Evaluator. Я мог бы добавить Type-Id в класс Var, поскольку type-id должен быть уникальным для приложения, и я должен указать его во время выполнения, стоит ли это сложности? пример реализации? возможно, с использованием класса Singleton для предоставления нового типа во время выполнения? - person uray; 04.03.2010
comment
Да, синглтон был бы уместен. Может быть, какой-нибудь класс-реестр, в котором должны быть зарегистрированы все операции и который будет назначать идентификаторы. - person Björn Pollex; 04.03.2010

Ваш пример кода содержит много ошибок, включая проблемы с нарезкой.

Я не уверен на 100%, но, кажется, помню, что вы можете использовать const type_info* как ключ для карты.

Если это так, вы можете использовать что-то вроде следующего. Он не свободен от RTTI (type_info), но поскольку Evaluator уже проверяет идентификаторы типов, вы можете использовать static_cast вместо dynamic_cast (но теперь это не так важно, поскольку код не ищет вслепую нужный оператор для применения. ).

Конечно, следующее полностью нарушено с точки зрения управления памятью. Пополнение с помощью умных указателей по вашему выбору.

#include <map>
#include <typeinfo>
#include <cassert>

#include <iostream>

struct CompareTypeinfo
{
    bool operator()(const std::type_info* a, const std::type_info* b) const
    {
        return a->before(*b);
    }
};

class Var {
public:
    virtual ~Var() {}
    virtual const std::type_info& getType() const = 0;

    virtual void print() const = 0;
};

template<typename T> class VarT : public Var {
    T value;
public:
    VarT(const T& v): value(v) {}
    const T& getValue() const { return value; }
    virtual const std::type_info& getType() const { return typeid(T); }  

    virtual void print() const { std::cout << value << '\n'; } 
};

class Operator {
public:
    virtual ~Operator() {}
    virtual Var* evaluate(const Var& a, const Var& b) const = 0;
    virtual const std::type_info& getType() const = 0;
};

template<typename T> class AddOperator : public Operator {
public:
    typedef T type;
    virtual const std::type_info& getType() const { return typeid(T); }
    virtual Var* evaluate(const Var& a, const Var& b) const
    {   
        //it is the responsibility of Evaluator to make sure that the types match the operator            
        const VarT<T>* varA = static_cast<const VarT<T>*>(&a);
        const VarT<T>* varB = static_cast<const VarT<T>*>(&b);

        return new VarT<T>(varA->getValue() + varB->getValue());
    }
};

class Evaluator {
private:
    typedef std::map<const std::type_info*, Operator*, CompareTypeinfo> TypedOpMap;
    typedef std::map<int, TypedOpMap> OpMap;
    OpMap operatorMap;
public:
    template <class Op>
    void registerOperator(int opId)
    {
        operatorMap[opId].insert(std::make_pair(&typeid(typename Op::type), new Op));
    }
    Var* evaluate(const Var& a, const Var& b,int opId)
    {
        OpMap::const_iterator op = operatorMap.find(opId);
        if (op != operatorMap.end() && a.getType() == b.getType()) {
            TypedOpMap::const_iterator typed_op = op->second.find(&a.getType());
            if (typed_op != op->second.end()) {
                //double-checked
                assert(typed_op->second->getType() == a.getType());
                return typed_op->second->evaluate(a, b);
            }
        }
        return 0;
    }
};

int main()
{
    Evaluator e;

    e.registerOperator<AddOperator<int> >(0);
    e.registerOperator<AddOperator<double> >(0);

    VarT<int> i1(10), i2(20);
    VarT<double> d1(2.5), d2(1.5);
    VarT<float> f1(1.0), f2(2.0);

    Var* i_result = e.evaluate(i1, i2, 0);
    Var* d_result = e.evaluate(d1, d2, 0);
    Var* f_result = e.evaluate(f1, f2, 0);
    Var* mixed_result = e.evaluate(i1, d2, 0);

    assert(i_result != 0);
    assert(d_result != 0);
    assert(f_result == 0); //addition not defined for floats in Evaluator
    assert(mixed_result == 0); //and never for mixed types

    i_result->print(); //30
    d_result->print(); //4.0
}
person visitor    schedule 04.03.2010
comment
да, код предназначен только для иллюстрации, это не настоящий код, я просто записываю его в блокнот, спасибо за ваше предложение, я буду читать ваши коды некоторое время ... - person uray; 04.03.2010