C++ Получить индекс таблицы виртуальных функций

Можно ли (и как) получить индекс виртуальной функции в таблице виртуальных методов?

class A
{
    virtual void foo();
}

я знаю foo это первый (0) элемент в таблице виртуальных методов

Однако я могу иметь foo и получить 0?

4 ответа

Несмотря на то, что vtables являются способом, которым большинство (вероятно, все) реализации C++ реализуют диспетчеризацию виртуальных функций, нет гарантии, что они даже существуют в стандарте, тем более что они находятся в определенном порядке.

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

Или иначе используйте невероятно специфичные для платформы хаки, чтобы получить фактический адрес функции-члена, найти виртуальную таблицу в памяти и найти адрес внутри.

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

В качестве примечания, как GCC, так и MSVC++ имеют документированные алгоритмы компоновки для своей таблицы vtable, а также документированные алгоритмы размещения vptr в объекте. Для GCC документация представляет собой Common C++ ABI (он же Itanium C++ ABI). Для MSVC++ я не знаю, где находится документация или существует ли она напрямую, но компилятор гарантирует, что, по крайней мере, классы без членов данных спроектированы так, чтобы быть совместимыми с COM ABI.

Как я уже говорил в комментариях, я подозреваю, что его реализация определяет детали реализации (спасибо @SteveJessop за то, что они указали правильные термины в комментариях к ответу) и зависит от ABI, поэтому (теоретически) это невозможно в портативный способ.
В качестве примера известного определения ABI см. Здесь (Itanium C++ ABI).
Другими словами, это жаргонный термин в стандарте C++ и означает - реализация должна документировать его.
Более того, как упомянуто в комментариях к вопросу @nm, стандарт не содержит ничего похожего на vtable, так что вряд ли он правил явно.
Реализации могут использовать их бесплатно или нет, и если они их используют, они могут предоставить поддерживаемые средства для доступа пользователя к коду или нет.

Тем не менее, опять же: нет явного, портативного способа сделать это.

Похоже, вы хотите выполнять виртуальную диспетчеризацию в общем виде, зная только сигнатуру функций, и получая ссылку на эту функцию другим способом.
Хотя это преследование может иметь свои преимущества, ваш вопрос связывает его с деталями реализации (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).

Другие вопросы по тегам