Използване на силата на виртуалните функции

Разгледайте следния примерен код:

class Base {
public:
    void f();
    virtual void vf();
};

class Derived : public Base {
public:
    void f();
    void vf();
};

#include <iostream>
using namespace std;

void Base::f() {
    cout << "Base f()" << endl;
}

void Base::vf() {
    cout << "Base vf()" << endl;
}

void Derived::f() {
    cout << "Derived f()" << endl;
}

void Derived::vf() {
    cout << "Derived vf()" << endl;
}

int main()
{
    Base b1;
    Derived d1;
    b1.f();
    b1.vf();
    d1.f();
    d1.vf();

    Derived d2;     // Derived object
    Base* bp = &d2; // Base pointer to Derived object
    bp->f();    // Base f()
    bp->vf();   // which vf()?

    return 0;
}

Резултатът от цикъла е:

Base f()

Base vf()

Derived f()

Derived vf()

Base f()

Derived vf()

Въпроси:

  1. В ред Base* bp = &d2 типът на обекта е известен по време на компилиране. Тогава решението коя функция да се използва в случай на bp->vf(); също може да бъде взето по време на компилиране, нали?

  2. Тъй като типът на обекта е известен по време на самото компилиране, силата на виртуалните функции използва ли се в тази примерна програма?


person nitin_cherian    schedule 14.12.2011    source източник
comment
До компилатора. Изглежда няма смисъл да го рационализираме.   -  person Lightness Races in Orbit    schedule 14.12.2011
comment
@TomalakGeret'kal : Значи в този пример силата на виртуалните функции не се използва, нали?   -  person nitin_cherian    schedule 14.12.2011
comment
Този въпрос наистина няма смисъл. Езикът предписва точно какво поведение получавате и точно това виждате. Какво неясно има в това? По-добре да попитате за цената на динамичното изпращане.   -  person Kerrek SB    schedule 14.12.2011
comment
@KerrekSB: Въпросът ми е дали програмата наистина използва силата на виртуалните функции, което е динамично обвързване?   -  person nitin_cherian    schedule 14.12.2011
comment
Мисля, че това, което @LinuxPenseur пита в част 2, е: Ще бъде ли конструирана таблица с указатели на виртуални функции за този пример?   -  person Lee Netherton    schedule 14.12.2011
comment
@ltn100 : Да, може да се постави и по този начин.   -  person nitin_cherian    schedule 14.12.2011
comment
Единственото нещо, което има значение, е възприеманото действие по време на изпълнение. Стандартът диктува как трябва да се изпълняват виртуалните функции, така че можете да очаквате да видите конкретно поведение по време на изпълнение. В този смисъл силата на виртуалните функции се запазва, докато виждате очаквания резултат. Компилаторът от друга страна е свободен да прави оптимизации, когато може, стига крайният резултат да е същият.   -  person Dave Rager    schedule 14.12.2011
comment
LinuxPenseur: от програмата се изисква да действа така, сякаш е действала, въпреки че отдолу може да използва преки пътища, стига да не можете да правите разлика. Толкова прост отговор: да, използва vtable.   -  person Mooing Duck    schedule 14.12.2011
comment
Вашият въпрос е грешен. Като се има предвид как програмата се очаква да се държи, можете да си представите най-общата реализация, която винаги ще работи. След това можете да попитате дали вашата конкретна програма изисква най-общата реализация за динамичното изпращане или дали компилаторът ще може да се възползва от статичната информация, за да оптимизира...   -  person Kerrek SB    schedule 14.12.2011


Отговори (4)


Тази програма е тривиална и наистина не демонстрира много добре силата на виртуалните функции (или по-общо полиморфизма). Помислете за тази промяна:

// add second derived class
class Derived2 : public Base {
public:
    void vf() { std::cout << "Derived2 vf()" << std::endl; }
};

// in main
int user_choice;
std::cin >> user_choice;
Base * ptr;
if (user_choice == 0)
    ptr = new Derived();
else
    ptr = new Derived2();
ptr->vf();

Тук изборът на клас зависи от въвеждането на потребителя - компилаторът няма начин да предскаже какъв тип обект ptr действително ще сочи към повикването ptr->vf();.

person Björn Pollex    schedule 14.12.2011

В ред Base* bp = &d2 типът на обекта е известен по време на компилиране. Тогава решението коя функция да се използва в случая на bp->vf(); също може да бъде взето по време на компилиране, нали?

Да.
Това е част от оптимизацията на компилатора, която повечето съвременни интелигентни компилатори ще могат да направят.

Ако компилаторът може да определи коя функция да извика по време на самото компилиране, тогава той ще го направи. Докато това зависи изцяло от компилатора дали може да открие точната функция, която да извика по време на компилиране или по време на изпълнение, виртуализмът ви гарантира поведението, което искате за вашата програма.

Тъй като типът на обекта е известен по време на самото компилиране, силата на виртуалните функции използва ли се в тази примерна програма?

Зависи изцяло от компилатора. Повечето съвременни компилатори ще могат да оценят това извикване на функция по време на компилация.

person Alok Save    schedule 14.12.2011

Ами... ДА и НЕ и на двата въпроса: зависи на какво ниво на абстракция спорите.

  • От езикова гледна точка 1) е погрешно схващане. Типът за d2 е известен, но когато дойде d2 адресът да бъде присвоен на bp, ще се извърши преобразуване от Derived* в Base*. От там нататък статичният тип на bp е Base* (защото това е неговата декларация), а динамичният тип, който сочи към неговия Произведен (защото това е, към което се отнася информацията за типа по време на изпълнение, свързана с обекта). От тях нататък всяка операция чрез bp приема Base* като тип и изисква пренасочване за всяка виртуална функция.

  • От гледна точка на компилатора може да се направи известна оптимизация и тъй като във всички ваши функции pb винаги сочи към Derived, виртуалното пренасочване може всъщност да бъде пропуснато. Но това се дължи на начина, по който е структуриран вашият конкретен пример, а не на езикова характеристика.

person Emilio Garavaglia    schedule 14.12.2011

In the line Base* bp = &d2, the object type is known at compile time. Then the decision of which function to use in the case of bp->vf(); can also be made at compile time right?

Не, решението коя функция да се използва се прави динамично по време на изпълнение въз основа на типа на обекта, а не на типа указател/препратка към този обект. Това се нарича динамично обвързване. Компилаторът поддържа скрит указател, наречен virtual pointer или vptr, който сочи към таблица, наречена виртуална таблица. Ще има една виртуална таблица на клас с поне една виртуална функция (независимо колко обекта са създадени за класа). Виртуалната таблица съдържа адреса на виртуалните функции на класа.

Since the object type is known at compile time itself, is the power of virtual functions used in this sample program?

Всъщност посоченият обект може да не е известен по време на компилиране. Вземете пример за метод, който приема указателя на базовия клас като параметър, както е показано по-долу:

void draw(Shape *bp)
{
    bp->draw();
}

В този случай действителният обект може да бъде всяка форма, получена от Shape. Но изчертаната форма зависи от действителния тип на предадения обект.

person Sanish Gopalakrishnan    schedule 14.12.2011
comment
Опасявам се, че отговорът ви на първия въпрос противоречи на отговора на Алс на същия въпрос. Мисля, че отговорът на Алс е даден след известно проучване на оптимизацията на компилирането. Но вашето обяснение с примерния код може да бъде съчетано с примера на Bjorn, за да получите добро разбиране на силата на виртуалните функции. Можете ли да проверите дали първият ви отговор е напълно правилен? - person nitin_cherian; 14.12.2011
comment
Имах предвид от гледна точка на програмисти. Компилаторът може да оптимизира извикването, но това може да зависи от нивото на абстракция. - person Sanish Gopalakrishnan; 14.12.2011