РЕФЕРАТ
В этой статье мы обсуждаем виртуальные функции в C ++. Часть ноль объясняет, как виртуальные функции объявляются и переопределяются. Первая часть пытается (и, возможно, не удается) объяснить, как реализованы виртуальные функции. Вторая часть - это пример программы, в которой используются примеры классов, определенных в нулевой и первой частях. Третья часть - классический пример животного, приведенный в каждой виртуальной функции - учебник по полиморфизму.
ЧАСТЬ НОЛЬ
Метод класса называется виртуальным тогда и только тогда, когда он объявлен таковым.
class my_base
{
public:
void non_virtual_test() { cout << 4 << endl; } // non-virtual
virtual void virtual_test() { cout << 5 << endl; } // virtual
};
(Конечно, я предполагаю, что программист ранее не делал ничего подобного #define virtual
.)
Класс, который повторно объявляет и повторно реализует не виртуальный метод одной из своих баз, называется перегрузкой этого метода. Класс, который повторно объявляет и повторно реализует виртуальный метод одной из его баз, называется переопределить этот метод.
class my_derived : public my_base
{
public:
void non_virtual_test() { cout << 6 << endl; } // overloaded
void virtual_test() { cout << 7 << endl; } // overriden
};
ЧАСТЬ ПЕРВАЯ
Когда компилятор обнаруживает, что класс имеет виртуальные методы, он автоматически добавляет таблицу виртуальных методов (также известную как vtable ) к макету памяти класса. Результат похож на то, что было бы сгенерировано при компиляции этого кода:
class my_base
{
//<vtable>
// The vtable is actually a bunch of member function pointers
protected:
void (my_base::*virtual_test_ptr)();
//</vtable>
// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
void virtual_test_impl() { cout << 5 << endl; }
// Initializing the real_virtual_test pointer in the vtable.
public:
my_base() : virtual_test_ptr(&my_base::virtual_test_impl) {}
public:
void non_virtual_test() { cout << 4 << endl; }
// The interface of the virtual function is a wrapper
// around the member function pointer.
inline void virtual_test() { *virtual_test_ptr(); }
};
Когда компилятор обнаруживает, что класс переопределил виртуальный метод, он заменяет связанную с ним запись в vtable. Результат похож на то, что было бы сгенерировано при компиляции этого кода:
class my_derived : public my_base
{
// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
void virtual_test_impl() { cout << 7 << endl; }
// Initializing the real_virtual_test pointer in the vtable.
public:
my_derived() : virtual_test_ptr(&my_derived::virtual_test_impl) {}
public:
void non_virtual_test() { cout << 6 << endl; }
};
ЧАСТЬ ВТОРАЯ
Теперь, когда ясно, что виртуальные функции реализованы с использованием vtables, которые являются ничем иным, как набором указателей на функции, должно быть понятно, что делает этот код:
#include <iostream>
using namespace std;
class my_base
{
public:
void non_virtual_test() { cout << 4 << endl; }
virtual void virtual_test() { cout << 5 << endl; }
};
class my_derived : public my_base
{
public:
void non_virtual_test() { cout << 6 << endl; }
void virtual_test() { cout << 7 << endl; }
}
int main()
{
my_base* base_obj = new my_derived();
// This outputs 4, since my_base::non_virtual_test() gets called,
// not my_derived::non_virtual_test().
base_obj->non_virtual_test();
// This outputs 7, since the vtable pointer points to
// my_derived::virtual_test(), not to my_base::virtual_test().
base_obj->virtual_test();
// We shall not forget
// there was an object that was pointed by base_obj
// who happily lived in the heap
// until we killed it.
delete base_obj;
return 0;
}
ЧАСТЬ ТРЕТЬЯ
Поскольку ни один пример виртуальной функции не обходится без примера с животными ...
#include <iostream>
using namespace std;
class animal
{
public:
virtual void say_something()
{ cout << "I don't know what to say." << endl
<< "Let's assume I can growl." << endl; }
/* A more sophisticated version would use pure virtual functions:
*
* virtual void say_something() = 0;
*/
};
class dog : public animal
{
public:
void say_something() { cout << "Barf, barf..." << endl; }
};
class cat : public animal
{
public:
void say_something() { cout << "Meow, meow..." << endl; }
};
int main()
{
animal *a1 = new dog();
animal *a2 = new cat();
a1->say_something();
a2->say_something();
}