Можно ли (и как) получить индекс виртуальной функции в таблице виртуальных методов?
class A
{
virtual void foo();
}
я знаю foo
это первый (0) элемент в таблице виртуальных методов
Однако я могу иметь foo
и получить 0?
Несмотря на то, что vtables являются способом, которым большинство (вероятно, все) реализации C ++ реализуют диспетчеризацию виртуальных функций, нет гарантии, что они даже существуют в стандарте, тем более что они находятся в определенном порядке.
Другими словами, единственный способ узнать это — взять свой конкретный компилятор, узнать, как он упорядочивает vtable, и затем следовать тому же алгоритму на основе макета класса, чтобы найти индекс для интересующей вас функции.
Или иначе используйте невероятно специфичные для платформы хаки, чтобы получить фактический адрес функции-члена, найти виртуальную таблицу в памяти и найти адрес внутри.
Но в любом случае, такая информация специфична для одной платформы и компилятора, возможно, даже для версии компилятора, в зависимости от гарантий ABI этого конкретного компилятора.
В качестве примечания, как GCC, так и MSVC ++ имеют документированные алгоритмы компоновки для своей таблицы vtable, а также документированные алгоритмы размещения vptr в объекте. Для GCC документация представляет собой Common C ++ ABI (a.k.a. Itanium C ++ ABI). Для MSVC ++ я не знаю, где находится документация или существует ли она напрямую, но компилятор гарантирует, что, по крайней мере, классы без членов данных спроектированы так, чтобы быть совместимыми с COM ABI.
Как я уже сказал в комментариях, я подозреваю, что это реализация определена подробности реализации (спасибо @SteveJessop за то, что они указали правильные термины в комментариях к ответу) и зависят от ABI, поэтому (теоретически) это невозможно сделать переносимым способом.
В качестве примера известного определения ABI см. Вот (Itanium C ++ ABI).
Другими словами, это жаргонный термин в стандарте C ++ и означает — реализация должна документировать это.
Более того, как @ n.m. упомянутые в комментариях к вопросу, стандарт не содержит ничего подобного виртуальные таблицы, так вряд ли это определено явно.
Реализации могут использовать их бесплатно или нет, и если они их используют, они могут предоставить поддерживаемые средства для доступа пользователя к коду или нет.
Тем не менее, опять же: нет явного, портативного способа сделать это.
Похоже, вы хотите выполнять виртуальную диспетчеризацию в общем виде, зная только сигнатуру функций, и получая ссылку на эту функцию другим способом.
Хотя это преследование может иметь свои преимущества, ваш вопрос связывает его с деталями реализации (vtable), что не гарантируется стандартом C ++.
К счастью для вас, стандарт предлагает способ вызова функции-члена, не зная ее имени, и он даже учитывает виртуальную диспетчеризацию. Это простой указатель на член (который вы можете хранить и копировать). Вы бы использовали это как это:
#include <iostream>
struct A
{
virtual void foo() { std::cout << "A::foo\n"; }
};
struct AA : A
{
virtual void foo() { std::cout << "AA::foo\n"; }
};
struct AAA : AA
{
virtual void foo() { std::cout << "AAA::foo\n"; }
};
void bar (A& a, void (A::* pMem)())
{
(a.*pMem)();
}
int main() {
A a;
AA aa;
AAA aaa;
bar (a, &A::foo);
bar (aa, &A::foo);
bar (aaa, &A::foo);
return 0;
}
В идеале мы можем зациклить адрес функции и сравнить адрес указанной функции, я написал следующий код на g ++, который мог бы выводить индекс виртуальной функции.
#include <iostream>
#include <stdio.h>
using namespace std;
class test {
public:
virtual void foo() {
cout << "foo" << endl;
}
virtual void goo() {
cout << "goo" << endl;
}
virtual void hoo() {
cout << "hoo" << endl;
}
};
int find_function_index(test* p, void* f) {
for(int i = 0; i < 3; ++i) {
void* tmp = (void*)*((long*)*(int*)(p)+i);
if(tmp == f)
return i;
}
return -1;
}
int main() {
test* p = new test();
void* f1 = reinterpret_cast<void*>(&test::foo);
cout << "foo: " << find_function_index(p, f1) << endl;
void* f2 = reinterpret_cast<void*>(&test::goo);
cout << "goo: " << find_function_index(p, f2) << endl;
void* f3 = reinterpret_cast<void*>(&test::hoo);
cout << "hoo: " << find_function_index(p, f3) << endl;
}
На следующем рисунке показан адрес test :: goo и значение f2 (в котором хранится правильный адрес test :: goo).