Виртуальные функции - взгляд "изнутри"

Рассмотрим на примере проигрывания музыкального трека.

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


class Track // base class
{
 public:

 virtual string const& Artist() = 0;
 virtual string const& Title() = 0;
 virtual void Play() = 0;
};

Класс Mp3Track наследуется от Track, и в нем возможно реализовать виртуальные функции из Track, необходимые для воспроизведения MP3. Класс WMATrack также наследуется от Mp3Track и воспроизводит WMA вместо MP3.


class Mp3Track : public Track // derived class
{
  public:
  virtual string const & Artist(); // Extract Artist info from ID tag
  virtual string const & Title(); // Extract track title info from ID tag
  virtual void Play(); // Play the audio data
};
 

Для воспроизведения используется функция DoMusic.
Этой функции не надо точно знать, какой трек воспроизводить. p->Play() вызовет Mp3Track::play() или WMATrack::play(), в зависимости от того, на объект какого класса указывает указатель p.


    
 void DoMusic(Track *p) {
  p->Play();
 }

 


Данный механизм реализуется следующим образом: каждый объект содержит указатель vptr, указывающий на таблицу vtable для соответствующего класса. Таблица vtable содержит указатели на виртуальные функции.
Ниже приведены исходники, в которых показано, как в действительности все работает.

 void Mp3Track_Play(struct Mp3Track *this);
 typedef void (*Fptr)(struct Mp3Track *);
 typedef Fptr (VTable)[4];

 struct Track
 {
    VTable const *vptr; // Hidden element
 };


 struct Mp3Track
 {
    struct Track mBase; // Hidden element
 };

 
 const VTable Mp3Track_vtable = 
 {
    (Fptr)Mp3Track_Artist,
    (Fptr)Mp3Track_Title,
    (Fptr)Mp3Track_Play
 };


 void DoMusic(Track *p)
 {
    (*(p->mBase.vptr[2]))(p);
 }

 Таблица виртуальных функций - список адресов виртуальных функций класса. Если класс А содержит виртуальную функцию и эта функция замещена подклассом В, то в этом случае адрес первоначальной функции заменяется адресом новой функции в таблице vtable. Это сделано для того, чтобы соблюсти требования полиморфизма. Когда объект класса В замещает объект класса А, тогда вызов виртуальной функции класса А использует эту таблицу и в соответствии с ней осуществляет вызов функции из класса В, а не А.

В С++ каждый объект содержит идентификационную информацию для обеспечения действий таких операторов как dyamic_cast и typeid, и для обработки исключений. Для классов, содержащих виртуальные функции, вместе с таблицей vtable включается блок информации, по которому dynamic_cast во время выполнения программы может определить тип интересующего объекта. При отсутствии таблцы vtable (это означает, что класс неполиморфичен), этот блок информации помещается только в эту область объектного кода, где используется этот класс.

Последнее изменение: Понедельник, 5 сентября 2011, 12:30