Виртуальная таблица содержит адрес каждой виртуальной функции для класса с известным смещением.
[Примечание: на практике, в отличие от обычного класса, vtables имеют элементы с отрицательным смещением, как указатель в середине массива. Это просто соглашение, которое не сильно меняет свободу реализации. В любом случае, единственная проблема заключается в том, что размещение информации в виртуальной таблице законодательно закреплено соглашением (ABI), и компиляторы, следуя одному и тому же, создают совместимый код для полиморфных классов.]
Что происходит, когда у вас есть дополнительные функции в производном классе? (не только функции, «унаследованные» от базового класса)
Как только вы примете идею о том, что указатель на структуру указывает как на весь объект, так и на его первый элемент, у вас появится представление о том, что указатель на производный класс указывает на базовый класс, который соответствующим образом расположен по нулевому смещению. Таким образом, вы можете иметь точно такое же значение указателя, представленное как void*
, которое можно использовать в качестве альтернативы для производного объекта или базы в соответствии с этим соглашением для одиночного наследования.
Теперь вы можете применить это к любой структуре данных и даже к виртуальной таблице, которая на самом деле является не таблицей (массивом элементов одного типа или значений, которые можно интерпретировать одинаково), а записью (объектов несвязанного типа или значение); вы можете видеть, что виртуальная таблица для такого производного класса может быть получена из виртуальной таблицы его уникальной базы точно таким же образом.
(Обратите внимание, что если вы компилируете C++ в C, вы можете столкнуться с правилами псевдонимов типов, когда делаете такие вещи. Конечно, у сборки нет такой проблемы, как и у наивно скомпилированного «ассемблера высокого уровня» C.)
Таким образом, для одиночного наследования база интегрируется и оптимизируется в производный класс:
- для элементов данных экземпляра (типа класса)
- а для членов виртуальных функций это члены данных vtable (или члены метакласса, если вы его себе представляете).
Обратите внимание, что размещение базы с нулевым смещением позволяет разместить базу vtable с нулевым смещением, что, в свою очередь, позволяет использовать тот же vptr, но не подразумевает его; и наоборот, совместное использование vptr с базой подразумевает, что базовая vtable находится на нулевом смещении (макет vtable = уровень метакласса), поэтому база должна быть на нулевом смещении (макет элементов данных = уровень класса).
И множественное наследование на самом деле является одиночным наследованием плюс, так как один класс всегда рассматривается как привилегированный: он размещается по нулевому смещению, поэтому указатели одинаковы, поэтому виртуальная таблица может быть размещена по нулевому смещению (поскольку указатели одинаковы); другие базы, не так.
Как мы видим, все унаследованные полиморфные классы, кроме одного, располагаются с ненулевым смещением при множественном наследовании. Каждый несет дополнительный «унаследованный» vptr в производном классе; этот (скрытый) указатель должен быть правильно заполнен любым производным конструктором.
Эти дополнительные vptr предназначены для базовых классов, которые встречаются с ненулевым смещением, поэтому указатель на унаследованную базу необходимо скорректировать (добавьте положительную константу для преобразования в базовый указатель, удалите ее, чтобы преобразовать обратно). То, что компилятор должен создать код для выполнения неявного преобразования, является тривиальным замечанием (преобразование целого числа в тип с плавающей запятой — гораздо более сложная задача); но здесь преобразование this
происходит между вызовом функции для данного базового типа и приземлением в функции, которая является переопределением в базовом или производном классе: разница в том, что настройка зависит от переопределения функции, которая известна только для class (экземпляр метатипа). Таким образом, vptr должен указывать на отдельную информацию vtable: ту, которая знает, как работать с этими преобразованиями базовых указателей в производные.
Как экземпляры «метатипа», vtables имеют всю информацию для автоматической настройки всех указателей. (Это зависит от конкретных задействованных типов классов и ни от каких других факторов.)
Итак, на уровне реализации есть два типа наследования:
- наследование с нулевым смещением; совместное использование vptr; называется первичным базовым классом в некоторых описаниях vtable и ABI;
- наследование произвольного смещения; наличие еще одного vptr; называется вторичным базовым классом.
Это для основных вещей. Виртуальное наследование гораздо более тонкое на уровне реализации, и даже концепция первичности не так ясна, поскольку виртуальные базы могут быть «первичными» производного класса только в некоторых других производных классах!
person
curiousguy
schedule
20.11.2019