Как получить индекс/смещение pure виртуальной ф-ии
Смещение для члена класса (pointer-to-member) можно получить с помощью макроса
он возвращает смещение члена относительно начала структуры/класса.
Для функций этот макрос не работает. Но чисто виртуальные ф-ии хранятся в таблице, которая находится в начале реализующего класса.
например:
{
public:
virtual int f1() = 0;
virtual int f2() = 0;
};
class CImplementation: public IInterface
{
public:
int f1() {return 0;};
int f2() {return 0;};
};
Индекс f1 = 0, f2 = 1
Смещение f1 = 0, f2 = 4 (для 32х битных машин)
Можно ли получить смещение или индекс ф-ий по их имени используя определения классов IInterface и/или CImplementation?
Но даже в конкретном случае для конкретного компилятора если нечто подобное получить можно, то только методом хака, что не есть хорошо для серьезного програмного продукта.
Кроме того, в отличие от поля, по полученному смещению можно будет только читать, писать можно будет опять же только с помощью хака (изменить атрибут доступа к памяти).
Поэтому рекомендую подойти к проблеме с другой стороны: а зачем тебе это вообще надо?
Может, можно все сделать иначе?
TfMethod ptr = &(TMyClass::Method); // Инициализация
TMyClass MyObject; TMyClass *pMyObject = &MyObject;
(MyObject.*)ptr (ПАРАМЕТРЫ); // Прямое использование
(pMyObject->*)ptr (ПАРАМЕТРЫ); // Косвенное использование
Кстати, если присавоить указателя адрес виртуального (даже абстрактного) метода, то его значением станет элемент таблицы вирт. функций. Соответственно, будет происходить виртуализация в зависимости от реального объекта.
Сама задача в кратце выглядит так:
имеется базовый класс-интерфейс. имеются несколько классов-интерфейсов наследующие базовый класс. имеется класс-контейнер базового класса.
{
virtual void notmatter() = 0;
};
class IInterface1: public IBase
{
public:
virtual void f1_1() = 0;
virtual void f1_2() = 0;
};
class IInterface2: public IBase
{
public:
virtual void f2_1() = 0;
virtual void f2_2() = 0;
};
class CContainer
{
IBase m_pBase[10];
};
В контейнере CContainer в качестве m_pBase могут быть как IInterface1 так и IInterface2. Названия и прототипы ф-ий, содержащихся в IInterface1 отличаются от содержащихся в IInterface2.
Необходимо, чтобы CContainer вызывал заданную ф-ию из всех объектов содержащихся в нем.
Например:
...
// инициализация контейнера объектами IInterface2
...
Container.Run(f2_1, /* параметры ф-ии*/);
Container.Run должна запустить ф-ию f2_1 для каждого из m_pBase.
Вот я и думал, если передавать в качестве параметра индекс виртуальной ф-ии, то можно будет вызывать ее по индексу (методом хака :) используя ассемблерную вставку)
Вот только скажи, каким образом функция f2_1 будет вызываться для екземпляра класса IInterface1, который не имеет такого метода, но может так же содежаться в твоем контейнере?
Кроме того, твой контейнер вызывает так же сомнения. Для чего он нужен? Почему ты задаешь вызов методов содержимого таким странным образом? Каким образом ты собираешься контролировать различные наборы параметров вызываемых методов?
Может лучше иметь контейнер функторов или отложенных вызовов?
Мне кажется, что у тебя серьезная ошибка в проектировании. Думаю (уверен), что можно сделать значительно красивее. Расскажи, постараемся помочь.
...
Кстати, если присавоить указателя адрес виртуального (даже абстрактного) метода, то его значением станет элемент таблицы вирт. функций. Соответственно, будет происходить виртуализация в зависимости от реального объекта.
Использование адреса метода класса (классический pointer-to-member-function) не подходит, поскольку приходится завязываться на конкретный класс:
можно вызвать IInterface2->*pf2_1, но не IBase->*pf2_1
если присвоить указателю адрес абстрактного метода, то он показывает на элемент статической таблицы виртуальных функций. А "виртуализация" достигается инициализированием регистра ecx (в MS-компиляторе так) перед вызовом. ecx = this pointer (или не через ecx, а через стек)
Вот только скажи, каким образом функция f2_1 будет вызываться для екземпляра класса IInterface1, который не имеет такого метода, но может так же содежаться в твоем контейнере?
Кроме того, твой контейнер вызывает так же сомнения. Для чего он нужен? Почему ты задаешь вызов методов содержимого таким странным образом? Каким образом ты собираешься контролировать различные наборы параметров вызываемых методов?
Мне кажется, что у тебя серьезная ошибка в проектировании. Думаю (уверен), что можно сделать значительно красивее. Расскажи, постараемся помочь.
отвечаю по пунктам: :)
ф-ия f2_1 не должна вызываться для IInterface1. за это отвечает программист. Контейнер состоит только из IInterface1 или только из IInterface2.
Контейнер содержит динамически поключаемые плагины (dll), которые инициализируются одинаковым способом, но имеют разные интерфейсы. все они выведены из IBase. На самом деле там не плоская цепочка (массив) элементов, а древовидная структура. Какой из child'ов дерева запускать определяет значение, которое вернула вызванная ф-ия parent'а. Т.е. берем root объект, вызываем ему, например, f2_2. ф-ия вернула, например, 3. Берем третий child, вызываем ему f2_2 и все повторяется до конца дерева.
Можно специализировать дерево например с помощью калбэков. Т.е. чтобы контейнер делал обход дерева, а в калбэк ф-ии производился собственно вызов f2_2 или f1_2 и т.д., но не хочется писать для каждой ф-ии каждого интерфейса IInterface* свою калбэк ф-ию.
Можно написать класс, наследующий CContainer и написать обертки для каждой из ф-ий IInterface*, но опять же - не хочется.. если изменится интерфейс - переписывать все обертки.
различные параметры можно передавать через ... (Variable Number of Arguments), а внутри Run() обрабатывать va_list и пихать их в стек (ассемблерная вставка)
ошибка в проектировании - согласен. но все-таки заманчивая идея, чтобы дерево само вызывало нужные ф-ии своих объектов :)
можно вызвать IInterface2->*pf2_1, но не IBase->*pf2_1
если присвоить указателю адрес абстрактного метода, то он показывает на элемент статической таблицы виртуальных функций. А "виртуализация" достигается инициализированием регистра ecx (в MS-компиляторе так) перед вызовом. ecx = this pointer (или не через ecx, а через стек)
Ерунда!
Убедись сам:
{
virtual void func() = 0;
};
struct B :A
{
virtual void func() {
std::cout << "B" << std::endl;
}
};
struct C :A
{
virtual void func() {
std::cout << "C" << std::endl;
}
};
int main()
{
void (A::*pf)() = &A::func;
A* pB = new B;
A* pC = new C;
(pB->*pf)();
(pC->*pf)();
delete pB;
delete pC;
return 0;
}
P.S. Не допускай глупой ошибки - пытаться оперировать языком С++, понятиями ассемблера. При чем тут регистры?
отвечаю по пунктам: :)
ф-ия f2_1 не должна вызываться для IInterface1. за это отвечает программист. Контейнер состоит только из IInterface1 или только из IInterface2.
Тогда есть смысл создать два разных контейнера для двух разных типов.
В этом тебе может помочь шаблон.
Контейнер содержит динамически поключаемые плагины (dll), которые инициализируются одинаковым способом, но имеют разные интерфейсы. все они выведены из IBase. На самом деле там не плоская цепочка (массив) элементов, а древовидная структура. Какой из child'ов дерева запускать определяет значение, которое вернула вызванная ф-ия parent'а. Т.е. берем root объект, вызываем ему, например, f2_2. ф-ия вернула, например, 3. Берем третий child, вызываем ему f2_2 и все повторяется до конца дерева.
Если вызываются виртуальные методы с одним именем (f2_2), то в чем проблема создать контейнер таких однотипных (с т.з. интерфейса) объектов?
Можно специализировать дерево например с помощью калбэков. Т.е. чтобы контейнер делал обход дерева, а в калбэк ф-ии производился собственно вызов f2_2 или f1_2 и т.д., но не хочется писать для каждой ф-ии каждого интерфейса IInterface* свою калбэк ф-ию.
Это я не понял.
различные параметры можно передавать через ... (Variable Number of Arguments), а внутри Run() обрабатывать va_list и пихать их в стек (ассемблерная вставка)
Плохая идея. Очень плохая.
Отказывайся от va_list и пр. void*, тем более если пишешь на C++.
ошибка в проектировании - согласен. но все-таки заманчивая идея, чтобы дерево само вызывало нузные ф-ии своих объектов :)
Ну вообще-то это обычная практика.
Только делается это несколько иначе.
См. паттерн "посетитель".
Почему бы тебе не сделать единый интерфейс для инициализации?
И передавать в него не набор параметров, а один параметр traits или context, т.е. объект содержащий всю необходимую для инициализации информацию.
Только делается это несколько иначе.
См. паттерн "посетитель".
Почему бы тебе не сделать единый интерфейс для инициализации?
И передавать в него не набор параметров, а один параметр traits или context, т.е. объект содержащий всю необходимую для инициализации информацию.
Ну в принципе у меня похоже и реализовано сейчас (если я правильно понял суть "посетителя"), я вот как раз думал как от этого уйти, потому, что для вызова каждой ф-ии надо писать обертку: метод Visit, объект traits или context и т.д...
Имеем классы:
{
public:
void f1();
int f2(int param);
...
complex f99(int param1, ..., float param12);
};
class CContainer
{
CNode m_Node[10];
};
Нужно иметь возможность вызвать для каждого из m_Node любую из ф-ий f1-f99
Схематически можно записать так:
Container.f1(); // вызывается ф-ия f1 для каждого из m_Node
int i = Container.f2(12); // вызывается ф-ия f2 для каждого из m_Node
// и т.д.
Одно условие: цикл перебирающий m_Node должен происходить внутри CContainer
{
CNode m_Node[10];
public:
void f1() {
for(int i=0; i<sizeof(m_Node)/sizeof(m_Node[0]); i++) {
m_Node.f1();
}
}
int f2(int param) {
for(int i=0; i<sizeof(m_Node)/sizeof(m_Node[0]); i++) {
m_Node.f2(param);
}
return some_value;
}
};
Ну вот проблема, что не хочется писать для каждой из 99и ф-ий обертку. Потому, что интерфейс может измениться и придется обертки переделывать.
И еще. Наряду с вызовом ф-ии производится еще некая обработка. Цикл выглядит будет примерно так для всех ф-ий f*:
m_Node.Init();
EnterCriticalSection(&CS);
m_Node.f1(); // m_Node.f2(param), m_Node.f99(...), etc
LeaveCriticalSection(&CS);
m_Node.Free();
}
Т.е. имеется некоторое количество кода, универсального для циклов всех ф-ий f1-f99, который не хочется просто так копировать. Единственное различие собственно вызов m_Node.f*();
В остальном такой метод хорош :)
Можно, конечно, сделать шаблонный класс из CContainer, который все сделает автоматически. Но это довольно сложно.
Значительно проще выяснить все ли 99 методов используются или привести все их или некоторые (т.е. сделать несколько групп однотипных вызовов) к одному виду, т.е. сделать одинаковые списки аргументов и возвращаемое значение.
По большому счету, даже типы аргументов не главное. Главное, чтоб количество аргументов было одинаковое.
Отсюда вывод: можно сделать все полностью автоматическим, но при этом придется нафигачить столько вариантов частной специализации шаблонов, сколько различных вариантов количества аргументов может быть.
Например, если количество аргументов от 0 до 16, значит должнео быть как минимум 17 вариантов специализации шаблонов.
В общем путь, где используются обертки понятен, можно использовать его в нескольких разных подходах. Интересно, можно ли что-нибудь сделать не используя обертки?
P.S. сделать аргументы одинаковыми у всех ф-ий - не получится. А если и получится, то тогда что - использовать pointer-to-member-function?