Я думаю, что о реализации виртуальной функции говорят много. Мой вопрос что относительно чистой виртуальной функции? Однако это реализовано? В виртуальной таблице, как сказать, это - чистое или нечистое? Что различие между чистой виртуальной функцией и виртуальной функцией с реализацией?
Обычно нет разницы в реализации между чистыми и нечистыми виртуальными функциями. Если определена чисто виртуальная функция, она действует как любая другая виртуальная функция. Если он не определен, проблема возникает только в том случае, если он вызывается явно.
Есть два основных различия в поведении, но обычно это не влияет на реализацию самого механизма виртуальных функций. Компилятор не должен позволять вам создавать объект типа, который имеет чистые виртуальные функции, которые не имеют нечистого окончательного переопределения в их иерархии наследования, и любые попытки сделать виртуальный вызов чистой виртуальной функции прямо или косвенно из конструктор или деструктор объекта вызывает неопределенное поведение.
Я не знаю фактической реализации, но хорошим вариантом было бы реализовать ее как указатель NULL
в vtable
. Другими словами, если у вас есть реализация, в vtable
есть действующий указатель на функцию, а если он чисто виртуальный, у вас есть указатель NULL
.
Это настолько логично, что я даже думаю, что это реализовано таким образом :-).
virtual void foo () = 0;
Чистая виртуальная функция по-прежнему является виртуальной функцией, поэтому она будет в vtable, но компилятор не требует для нее реализации и запретит создание экземпляра базовый класс, объявляющий чистый виртуальный. Поскольку вы все еще можете разыменовать указатели типа абстрактного базового класса, виртуальная таблица должна иметь запись для полиморфизма и привязки во время выполнения.
Это помогает?
Это не ответ, а скорее продолжение комментариев к этот ответ выше
. Язык C ++ определяет, как действует механизм виртуальной диспетчеризации во время построения. При создании экземпляра объекта в иерархии вызывается базовый конструктор (*). На этом этапе инициализируется виртуальный механизм диспетчеризации для базового класса. На этом этапе виртуальные функции будут отправлены в базовую реализацию. Любой вызов виртуального метода, не являющегося чистым, с использованием механизма виртуальной диспетчеризации (без явной квалификации класса) вызовет базовую реализацию.
После завершения базового конструктора виртуальный механизм диспетчеризации (обычно vtable) сбрасывается до версии производного типа, и любой динамический вызов оттуда будет вызывать производную версию методов:
struct base {
virtual void non_pure() { std::cout << "base::non_pure" << std::endl; }
virtual void pure_not_implemented() = 0;
virtual void pure_implemented() = 0;
base() { // at this point the object is a ´base´
non_pure(); // base::non_pure
// pure_not_implemented(); // runtime error: pure virtual method called
pure_implemented(); // base::pure_implemented
// base::pure_not_implemented(); // link error
}
};
void base::pure_implemented() { std::cout << "base::pure_implemented" << std::endl; }
struct derived : base {
virtual void non_pure() { std::cout << "derived::non_pure" << std::endl; }
virtual void pure_not_implemented() { std::cout << "derived::pure_not_implemented" << std::endl; }
virtual void pure_implemented() { std::cout << "derived::pure_implemented" << std::endl;
derived() { // after the implicit call to the base class
// this is a ´derived´ object, now calls will
// get dispatched to derived:: implementations
non_pure(); // derived::non_pure
pure_not_implemented(); // derived::pure_not_implemented
pure_implemented(); // derived::pure_implemented
base::non_pure(); // base::non_pure
// base::pure_not_implemented() // link error
}
};
Обратите внимание, что есть различия между использованием механизма динамической отправки (обычно vtable) и вызовом определенного метода с полным именем. Вызов нереализованного чистого виртуального метода через полную квалификацию будет разрешен во время компиляции, но завершится ошибкой во время компоновки (компоновщик не может найти реализацию для вызова).
Это важное решение при разработке языка.Другой вариант (тот, который выбрала Java) - инициализация механизма виртуальной диспетчеризации наиболее производным типом с самого начала, до вызова конструктора базового класса. Проблема этого подхода заключается в том, что если базовый конструктор вызывает виртуальный метод (который в Java есть у всех), он будет отправлен в наиболее производную реализацию и, таким образом, будет выполняться в еще не созданном объекте, что может привести к неожиданным результатам:
public class Base
{
public Base() {
f();
}
public void f() {
System.out.println("Base.f");
}
}
public class Derived extends Base {
public final int constant;
public Derived() { constant = 5; }
public void f() {
System.out.println( "Derived.f() " + constant );
}
public static void main( String args[] ) {
Derived d = new Derived(); // prints Derived.f() 0
}
}
В версии Java механизм динамической отправки с самого начала считает, что объект имеет тип Derived
. Вызов f ()
в базовом конструкторе будет динамически отправлен производной реализации. В приведенном выше примере, даже если переменная объявлена как final и, следовательно, является константой со значением 5 (кажется очевидным в коде), фактическое напечатанное значение равно 0, потому что конструктор Derived
не выполнил .
(*) Это чрезмерное упрощение, но детали на самом деле не влияют на аргумент.