PDA

Просмотр полной версии : таблица пойнтеров на методы разных классов


crawler
30.04.2007, 11:46
Вопрос к гуру С++ :
Имеется в наличии

void C1::m1(int );
void C1::m2(int );
void C2::m3(int );
void C2::m4(int );
- у всех методов одинаковые входные и выходные параметры

нужно сделать такой выкрутас чтобы
а) хранить таблицу пойнтеров на методы (заметьте разных классов - кастинг в пойнтер войд не предлагать)
б) в зависимисти от инпута вызывать соответсвующий метод по пойнтеру: т.е если инпут 1 - вызывать по пойнтеру на C1::m1(), а если 3 - вызывать C2::m3()

Сегодня у меня есть уже класс для вызова методов но только для одного класса, и я вызываюнужный метод по (m_pBaseClass->*pMethod)(nIntValue) . Как сделать это для нескольких (или хотя бы 2) классов.
Заранее спасибо.

EvroStandart
30.04.2007, 13:39
Я конечно не гуру С++, но если у тебя куча классов с одинаковыми методами, это похоже на стандартный фабричный шаблон.

http://www.google.ee/search?hl=et&client=firefox-a&channel=s&rls=org.mozilla%3Aen-US%3Aofficial&hs=rQ7&q=C%2B%2B+factory+pattern&btnG=Otsi&lr=

crawler
30.04.2007, 15:10
похоже на правду. А примерчик накидать можно? как сделать
C3 executor;
executor.run(3, 18);
чтобы вызвалась C2::m3(18)

P.S. я не программер, и пример жизненно необходим. Если C1 и C2 наследники С0 - это что-то меняет?

Drakosha
30.04.2007, 16:08
Предложение: не надо хранить указатели на функции. Храни функторы (functor). Т.к. это нормальные объекты то делаешь наследование и всё ок.
Вот объяснение: http://en.wikipedia.org/wiki/Function_object

crawler
01.05.2007, 13:51
Повторю еще раз Я -НЕ ПРОГРАММИСТ. Мне нужно сделать так, чтобы я мог запускать методы разных классов не зная их имени и порядок во время компиляции.

Фраза Т.к. это нормальные объекты то делаешь наследование и всё ок мне ничего не говорит. чем "нормальные объекты" отличаются от ненормальных ? зачем делать наследование?:idontnow:


class T;
class Run
{
typedef int (T::*Method)(int );
typedef struct {
int index
public:
Run(T* pT) { m_pBaseClass = pT; };

int Call( int nMethIndex, int nParam)
{
int res;
if (nMethIndex == 1)
res = (m_pBaseClass->*m_m1)(nParam);
if (nMethIndex == 2)
res = (m_pBaseClass->*m_m2)(nParam) ;
return res;
}
void Map( Method m1, Method m2) { m_m1 = m1; m_m2 = m2;}
private:
T* m_pBaseClass;
Method m_m1;
Method m_m2;
};
Как вот это расширить на 2 класса ?

Drakosha
01.05.2007, 14:08
примерно так выглядят фанкторы:

class Functor {
public:
virtual void Call(Smth & smth) = 0;
};

class FunctorA : public Functor {
public:
virtual void Call(Smth & smth) { smth->RunA(); }
};

class FunctorB : public Functor {
public:
virtual void Call(Smth & smth) { smth->RunB(); }
};

в таблице ты хранишь указатели: Functor*
надеюсь стало понятнее

crawler
01.05.2007, 14:24
class C1;
class C2;

class FunctorA : public Functor {
public:
virtual void Call( C1 & smth) { smth->RunA(); }
};

class FunctorB : public Functor {
public:
virtual void Call(C2 & smth) { smth->RunB(); }
};

...

Functor fTable[2];
... fill table here
...
fTable[0].Call(); // вызов RunA();
fTable[1].Call();// вызов RunВ();


что-то в этом роде ?

Drakosha
01.05.2007, 14:32
к сожалению, прототип Call должен быть одинаков во всех классах. Поэтому может так:


class C1;
class C2;

class FunctorA : public Functor {
public:
virtual void Call( Input & input) { m_c1.Do(input); }
C1 & m_c1;
};

class FunctorB : public Functor {
public:
virtual void Call(Input & input) { m_c2.Do(input); }
C2 & m_c2;
};

crawler
01.05.2007, 18:06
чего-то слишком сложно получается... Я ведь с самого начала хотел избежать выраженного вызова m_c1.Do() Если несколько методов у каждого класса C1, C2, то как я с ними расправлюсь?
... или я чего не понимаю ?

пока додумался использовать темплейты, но как сделать для 2 классов одновременно - не понимаю

template <typename BaseClass>
class Caller
{
typedef int (BaseClass::*Method)(int);

public:
Caller (BaseClass *pInstance) { m_pInstance = pInstance; }
void Map ( std::string Name, Method mPtr) { m_Method[Name]= mPtr; }
int Call ( std::string Name, int param) {
if (m_Method.find(Name)!=m_Method.end())
return (m_pInstance->*(m_Method[Name]))(param);
else return 0;
}
private:
std::map<std::string,Method> m_Method;
BaseClass *m_pInstance;
};

Drakosha
01.05.2007, 18:14
Я наверное сам ничего не понимаю, запутался. У тебя есть:
class B;
class D1 : public B;
class D2 : public B;

почему бы не определить методы m... виртуальными и держа указатель на B просто вызывать их?

PSyton
01.05.2007, 19:58
Для начала неплохо было бы понять ради чего конкретно городить огород, вообще? А уж потом решать как это организовывать. Не совсем понятная постановка задачи.
Я так понял что имеется мношество каких-то алгоритмов, которые прменяются к инту в зависимости от контекста, тогда возможно что следует просто завести множество унифицированных классов алгоритмов и сделать для них общий автоматический азпускальшик по контексту?

crawler
02.05.2007, 10:35
Для чего это нужно - я написал простенький интерпретатор, заточенный под мои нужды. У меня есть несколько алгоритмов которые применяются к интерфейс-классу параметров (в примере заменено на int). Алгоритмы запускаются один за другим в какой-то последовательности, заданной в файле конфигурации. Я читаю файл конфигурации и запускаю алгоритмы в том порядке, как в нем указано.
У меня есть несколько уже существующих классов для разных семейств алгоритмов. Пока я работаю с каким-то одним классом -все нормально, тот код что я привел в посте #9 меня полностью устраивает: там есть инстанс (я имею в виду инициализированный об'ект - пользуююсь своим жаргоном) класса, и я запускаю его методы как мне и нужно.
Но если мне надо работать с 2 классами одновременно - то мне нужно какое-то образование, чтобы держало инстансы всех нужных мне классов, и вызывало их методы в зависимости от входных данных. Надо учесть что классы алгоритмов уже написаны и их сильно менять не желательно -сегодня я просто добавил "обертку" на существующие методы, чтобы унифицировать интерфейс.
множество унифицированных классов алгоритмов и сделать для них общий автоматический азпускальшик по контексту именно это я и пытаюсь сделать, но не знаю как

как бы я хотел, чтобы это выглядело в коде :
class CAlg1; // has methods m11(), m12(), m13()
class CAlg2; // has methods m11(), m12(), m13()
// all method have same declaration int mXX( int )
...
CAlg1 a1(1,2,3); // initialize class
CAlg2 a2(3,4); // initialize class

Caller interp(a1, a2); // initialize Caller class with necessary algoritihms instances

// Set mapping from name to method
interp.Map("m11", &CAlg1::m11);
interp.Map("m12", &CAlg1::m12);
interp.Map("m13", &CAlg1::m13);
interp.Map("m21", &CAlg2::m21);
interp.Map("m22", &CAlg2::m22);
...
interp.Call("m11", 0); // call specific algorithm
interp.Call("m21", 10); // call specific algorithm
while ( /* read method and params from file */ )
interp.Call(method, params);

PSyton
02.05.2007, 12:51
Так и думал что это интерпретатор. :-)
Доберусь домой, попробую накропать маленький приерчик.

EvroStandart
02.05.2007, 14:46
но как сделать для 2 классов одновременно - не понимаю

http://www.codesampler.com/miscsrc.htm
Тут я нашёл простой пример фабрики с коментами.
Гугл рулит. :)

crawler
02.05.2007, 16:01
EvroStandart, Спасибо, но сюда фабрика не подходит. Фабрика дает выбрать какой именно об'ект инициализировать. В моем случае я должен держать инстансы обеих классов, и вызывать подходящий метод из какого-то одного. Хотя на той же странице я нашел "Chain of Responsibility Design Pattern" - это судя по всему то что мне нужно. Сча буду разбираться.

PSyton
02.05.2007, 17:29
Естественно это не скомпилируется сразу - это только первые наметки, еслои так можно сказать. Суть данного метода в том что можно сделать как раз обертку на уже готовый функционал. С помощью шаблонов все это можно расширить таким образом чтобы при наследовании от уже написанного ранее класса алгоритма (не по данной схеме) его конструктор вызывался автоматом, после чего можно получить более красивые вещи, например, для регистрации алгоритмов использовать более хитрые шаблоны, более корректно проверяющие что им подают в качестве параметров шаблона...
Еще раз повторяю что все что приведено ниже это просто наметки и намеки, на то как можно сделать.

class ArgumentsList
{
private:
//...
public:
ArgumentsList() {}
ArgumentsList(const ArgumentsList& other)
{
*this = other;
}
ArgumentsList &operator=(const ArgumentsList& other)
{
if (this != &other)
{
//Копируем все что надо
}
return *this;
}
~ArgumentsList() {};
};

class Results
{
public:
Results();
//Вся реализация конструкторы копирования и т.п.
~Results();
};

// Базовый клас алгоритма.
class BaseAlgoritm
{
private:
std::string m_invokedMethod;
protected:
void dummyTest();
public:
IBaseAlgoritm(const std::string& methodName)
: m_invokedMethod(methodName)
{
}
virtual ~IBaseAlgoritm()
(
)
virtual const std::string& invikedMethod() const
{
return m_invokedMethod;
}
virtual bool canProccess(const std::string& methodName)
{
return (methodName == m_invokedMethod);
}
virtual Results proccess(const ArgumentsList& args) = 0;
};


class Algoritm
: public BaseAlgoritm
{
public:
Algoritm(const std::string& methodName)
: BaseAlgoritm(methodName)
{
dummyTest();
}
virtual ~Algoritm()
(
)
};


//Предположим что есть класссы A1 и B1 которые что-то там делают... Нам нужно на их основе написать свои алгоритмы...

//Делаем наследника от A1

class AA1
: public A1
, public Algoritm
{
AA1(int a, int b, int c)
: A1(a, b, c)
, Algoritm("m1")
{}
virtual ~AA1() {}
//Реализуем функцию которая будет выполнять действия...
virtual Results proccess(const ArgumentsList& args)
{
retrun A1::m1(args);
}
};

//Аналогично для B1

class BB1
: public B1
, public Algoritm
{
BB1(int a, int b)
: B1(a, b)
, Algoritm("m2")
{}
virtual ~BB1() {}
//Реализуем функцию которая будет выполнять действия...
virtual Results proccess(const ArgumentsList& args)
{
retrun B1::m2(args);
}
};

// так далее. Суть в том что каждый наследник класса-примеси Algoritm обязан реализовать proccess, таким образом имеем общий интерфейс для все алгоритмов реализованных подобным образом.
// По идее можно не использовать примесь в виде уже реализованного класса, а просто делать наследника от Algoritm и реализовывать его соотв образом.
// Интерпретатор примет рпимерно такой вид:

class Interpret
{
private:
typedef std::list<BaseAlgoritm *> AList;
AList m_algoritms;
public:
Interpret() {}
virtual ~Interpret()
{
while (m_algoritms.size() > 0)
{
delete *(m_algoritms.begin());
m_algoritms.erase(m_algoritms.begin());
}
}
Results invore(const std::string &method, const ArgumentsList& args)
{
AList::const_iterator i = m_algoritms.begin();
AList::const_iterator ei = m_algoritms.end();
for (; i != ei; ++i)
{
if ((*i)->canProccess(method))
return (*i)->proccess(args)
}
return Results();
}
bool contains(const std::string &method)
{
AList::const_iterator i = m_algoritms.begin();
AList::const_iterator ei = m_algoritms.end();
for (; i != ei; ++i)
{
if ((*i)->canProccess(method))
return true;
}
return false;
}
template <class AT>
void register(int a, int b, int c)
{
AT* newalg = new AT(a, b, c);
if (!contains(newalg->invikedMethod()))
m_algoritms.push_back(newalg);
else
delete newalg;
}
template <class AT>
void register(int a, int b)
{
AT* newalg = new AT(a, b);
if (!contains(newalg->invikedMethod()))
m_algoritms.push_back(newalg);
}
};


int _tmain(int argc, _TCHAR* argv[])
{
Interpret tmp;
tmp.register<AA1>(10,5.3);
tmp.register<BB1>(5,22);
ArgumentsList lst1;
ArgumentsList lst2;
Results rr = tmp.invore("m1", lst1);
Results rr1 = tmp.invore("m2", lst2);
}

Flexz
03.05.2007, 11:23
посмотри boost::function

конкретно вот это:
http://boost.org/doc/html/function/tutorial.html#id2688553

PS сильно не вникал в суть дискуссии, так что может быть мимо :rolleyes:

crawler
04.05.2007, 18:20
PSyton, попытался расширить с помощь шаблонов - умудрился вышибить компайлер с ексепшеном :eek: послезавтра продолжу эксперимент - сделаю без расширения :)

Flexz, не это то что надо. Я тоже наткнулся на несколько решений - но чтобы их исползовать надо понять как - а весь boost написан так, что мне проще написать самому, чем разобраться как его пользовать.

--------------------------------
Сделал. В интерпретаторе хранятся пойнтеры на базисный класс, темплейтом сделал регистрацию с разным количеством входных переменных, и промежуточный класс тоже темплейтный. Вызывается виртуальные метод "Call" базисного класса, и из-за инициации наследников подходящими методами все работает.
Таким образом вызов выглядит так:

C1 c1;
C2 c2;

Interpreter i;

i.Register<C1> ( &c1,
"m11",&C1::m1,
"m12",&C1::m2,
NULL );
i.Register<C2> ( &c2,
"m21",&C2::m1,
"m22",&C2::m2,
"m23",&C2::m4,
NULL );

i.Call("m23",12);



Огромное спасибо всем участникам дискуссии.