#опытным
Всем нам как С++ разработчикам интересно, как все устроено внутри всего. Вызвано это профессиональной деформацией или врожденным позывом, но у нас у всех это есть. Поэтому сейчас заглянем немного под капот...
Полиморфизм - там штука, которая сделала ООП и ОО дизайн такими мощными и широкоиспользуемыми инструментами. За счет чего он реализован в С++?
Наиболее распространенное решение - vtable. Таблица виртуальных методов. Не буду сильно погружаться, но в сущности, это массив, который хранит указатели на методы класса. И сейчас мы попробуем вызвать метод класса напрямую через этот массив используя низкоуровневые преобразования.
struct Base {
virtual void func1() {
std::cout << i+1<< std::endl;
}
virtual void func2() {
std::cout << i+2 << std::endl;
}
};
struct Derived: Base {
void func1() override {
std::cout << i+3 << std::endl;
}
void func2() override {
std::cout << i+4 << std::endl;
}
};
int main() {
auto basePtr = std::unique_ptr<Base>(new Derived);
uint64_t ** pVtable = (uint64_t **)basePtr.get();
printf("Vtable: %p\n", pVtable);
printf("First Entry of VTable: %p\n", (void*)pVtable[0][0]);
printf("Second Entry of VTable: %p\n", (void*)pVtable[0][1]);
using VoidFunc = void (*) (void*);
VoidFunc firstMethod = (VoidFunc) pVtable[0][0];
firstMethod(basePtr.get());
VoidFunc secondMethod = (VoidFunc) pVtable[0][1];
secondMethod(basePtr.get());
}Для начала нам нужна иерархия объектов. У родителя и ребенка по 2 метода, каждый выводит определенный инкремент целочисленного поля класса. Теперь создаем умный указатель родительского класса, который содержит объект ребенка.
Далее нужно найти указатель на vtable. Обычно он всегда лежит в первых 8 байтах объектов полиморфных классов. Так как у нас двухуровневая косвенность (указатель на объект, а в нем указатель на таблицу), мы объявляем двойной указатель на 8-мибайтный инт и он и будет нашим vtable_ptr. Почему uint64_t? Да на самом деле косвенность трехуровненая, потому что в ячейках vtable лежат указатели на методы, а указатель легко представить в виде 8-байтного числа.
Самое сложное позади, мы нашли все, что нужно. Осталось только кастануть инты, лежащие в ячейках vtable к указателям на функцию правильного типа. И вуаля. Готово. Возможный вывод:
Vtable: 0x5a7841237eb0
First Entry of VTable: 0x5a781fd28ae2
Second Entry of VTable: 0x5a781fd28b22
3
4
Где это знание нужно?
Ну вообще в принципе неплохо разбираться в том, как работает полиморфизм в С++. У всего есть свои особенности и в какой-то необычной ситуации эти знания вам могут помочь пофиксить багу.
Знание внутреннего устройства инструмента позволить вам понимать его преимущества и недостатки. Возможно в проектах с высокими требованиями к производительности(слабое железо как в embedded или супер-ультра-гипер low latency как в hft), вам предстоит выбирать между динамическим полиморфизмом и статическим полиморфизмом aka шаблонами. Чтобы сделать выбор качественно и аргументировать его, нужно знание базы.
А как говорится, пока сам не потрогаешь ручками - не поймешь.
Только не нужно так писать в продовом коде! А то будет, как на картинке внизу.
Stay hardwared. Stay cool.