Віртуальна / чиста віртуальна пояснена


346

Що саме означає, якщо функція визначена як віртуальна і чи така ж, як і чиста віртуальна?

Відповіді:


339

З віртуальної функції Вікіпедії ...

У об'єктно-орієнтованому програмуванні на таких мовах, як C ++ та Object Pascal, віртуальна функція або віртуальний метод - це успадкована і перезаписувана функція або метод, для якого сприяє динамічне відправлення. Ця концепція є важливою частиною (під час виконання) поліморфізму частини об'єктно-орієнтованого програмування (ООП). Коротше кажучи, віртуальна функція визначає цільову функцію, яка повинна виконуватися, але ціль може бути невідома під час компіляції.

На відміну від невіртуальної функції, коли віртуальну функцію переосмислено, найпохідніша версія використовується на всіх рівнях ієрархії класів, а не лише на тому рівні, на якому вона була створена. Отже, якщо один метод базового класу викликає віртуальний метод, замість версії, визначеної в базовому класі, буде використана версія, визначена у похідному класі.

Це на відміну від невіртуальних функцій, які все-таки можуть бути замінені у похідному класі, але "нова" версія буде використовуватися лише похідним класом та нижче, але взагалі не змінить функціональність базового класу.

тоді як ..

Чиста віртуальна функція або чистий віртуальний метод - це віртуальна функція, яку потрібно реалізувати похідним класом, якщо похідний клас не є абстрактним.

Коли існує чистий віртуальний метод, клас є "абстрактним" і не може бути ідентифікований самостійно. Натомість повинен бути використаний похідний клас, який реалізує чисто-віртуальні методи. Чисто-віртуальний взагалі не визначений у базовому класі, тому похідний клас повинен визначати його, або цей похідний клас також абстрактний і не може бути ідентифікований. Тільки клас, який не має абстрактних методів, може бути екземпляром.

Віртуальний забезпечує спосіб перекриття функціональності базового класу, а чисто-віртуальний цього вимагає .


10
Отже ... чистий віртуальний ключове слово, або просто термін, який використовується?
Джастін

197
функція віртуальної пустоти () = 0; є чистою віртуальною. Значення "= 0" - це чистота.
Goz

8
Джастін, "чистий віртуальний" - це лише термін (не ключове слово, див. Мою відповідь нижче), який використовується для означання "ця функція не може бути реалізована базовим класом. Як сказав Гоз, додавши" = 0 "до кінця віртуального Функція робить його "чистим"
Нік Хаддад

14
Я вважаю, що Stroustrup сказав, що він хотів додати pureключове слово, але Bell Labs збирається зробити великий реліз C ++, і його менеджер не дозволить цього на пізньому етапі. Додавання ключових слів - велика справа.
кварк

14
Це не є гарною відповіддю. Будь-який метод може бути переопрацьований, не лише віртуальний. Дивіться мою відповідь для отримання більш детальної інформації.
Асик

212

Я хотів би прокоментувати визначення віртуальної Вікіпедії, яке повторило декілька тут. [На момент написання цієї відповіді] Вікіпедія визначила віртуальний метод як той, який можна перекрити в підкласах. [На щастя, Wikipedia редагується з цього часу, і тепер це правильно пояснюється.] Це неправильно: будь-який метод, не лише віртуальний, може бути замінений у підкласах. Що віртуальне - це надати вам поліморфізм, тобто можливість вибору під час виконання найбільш перетвореного методу .

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

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

Який вихід цієї програми?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Похідне переосмислює кожен метод Base: не лише віртуальний, але й невіртуальний.

Ми бачимо, що коли у вас є Base-pointer-to-Derived (bDerived), виклик NonVirtual викликає реалізацію класу Base. Це вирішується під час компіляції: компілятор бачить, що bDerived - це Base *, що NonVirtual не є віртуальним, тому робить роздільну здатність для класу Base.

Однак виклик Virtual викликає реалізацію класу похідних. Через віртуальне ключове слово, вибір методу відбувається під час виконання , а не під час компіляції. Що відбувається тут під час компіляції, це те, що компілятор бачить, що це Base * і що він викликає віртуальний метод, тому він вставляє виклик в vtable замість класу Base. Цей vtable інстанціюється під час виконання, отже, роздільна здатність часу для перетворення найбільшої кількості.

Я сподіваюся, що це було не надто заплутано. Коротше кажучи, будь-який метод може бути відмінений, але лише віртуальні методи дають вам поліморфізм, тобто підбір часу найбільшої похідної зміни. На практиці, однак, перекриття невіртуального методу вважається поганою практикою і використовується рідко, тому багато людей (включаючи тих, хто написав цю статтю у Вікіпедії) вважають, що можна відмінити лише віртуальні методи.


6
Тільки тому, що стаття у Вікіпедії (яку я жодним чином не захищаю) визначає віртуальний метод "як такий, який можна перекрити в підкласах" не виключає можливості того, що інші, невіртуальні, методи з такою ж назвою можуть бути оголошені. Це відомо як перевантаження.

26
Визначення є, однак, невірним. Метод, який можна перекрити у похідному класі, не є віртуальним за визначенням; чи може бути відмінений метод, не має значення для визначення "віртуального". Також "перевантаження" зазвичай означає, що в одному класі є кілька методів з одним іменем та типом повернення, але різними аргументами; це дуже відрізняється від "переосмислення", що передбачає абсолютно той самий підпис, але у похідному класі. Коли це робиться неполіморфно (не-віртуальна основа), його часто називають «приховуванням».
Асик

5
Це має бути прийнятою відповіддю. Ця конкретна стаття у Вікіпедії, яку я знайду час для посилання тут, оскільки ніхто з цього питання не займався , - це сміття. +1, добрий пане.
Йосафатв

2
ЗАРАЗ це має сенс. Дякую, добрий пане, що правильно пояснив, що будь-який метод може бути замінений похідними класами, і зміна полягає в тому, як компілятор буде вести себе, щоб вибрати, яку функцію викликають у різних ситуаціях.
Doodad

3
Можливо, буде корисно додати Derived*дзвінки з тією ж функцією, щоб керувати точкою додому. Інакше чудова відповідь
Джефф Джонс

114

Віртуальне ключове слово надає С ++ свою здатність підтримувати поліморфізм. Коли у вас є вказівник на об’єкт деякого класу, такий як:

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

У цьому (дурному) прикладі функція GetNumberOfLegs () повертає відповідне число на основі класу об'єкта, для якого він викликається.

Тепер розглянемо функцію 'SomeFunction'. Не байдуже, який тип тваринного об’єкта передається йому, доки він походить від Тварини. Компілятор автоматично передає будь-який клас походження Animal тварині, оскільки це базовий клас.

Якщо ми це зробимо:

Duck d;
SomeFunction(&d);

він буде виводити "2". Якщо ми це зробимо:

Horse h;
SomeFunction(&h);

він буде виводити "4". Ми не можемо цього зробити:

Animal a;
SomeFunction(&a);

тому що вона не буде компілюватися через чисту віртуальну функцію GetNumberOfLegs (), що означає, що вона повинна бути реалізована шляхом отримання класів (підкласів).

Чисті віртуальні функції в основному використовуються для визначення:

а) абстрактні заняття

Це базові класи, де ви маєте виходити з них, а потім реалізовувати чисті віртуальні функції.

б) інтерфейси

Це "порожні" класи, де всі функції є чистими віртуальними, отже, ви повинні отримати та реалізувати всі функції.


У вашому прикладі ви не можете зробити номер 4, оскільки ви не забезпечили реалізацію чистого віртуального методу. Це не суворо, тому що метод є чисто віртуальним.
iheanyi

@iheanyi Ви не можете забезпечити реалізацію чистого віртуального методу в базовому класі. Отже, випадок №4 все ще є помилкою.
prasad

32

У класі C ++ віртуальний - це ключове слово, яке позначає це, метод може бути замінений (тобто реалізований) підкласом. Наприклад:

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

У цьому випадку підклас може замінити функцію initShape для виконання деяких спеціалізованих робіт:

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

Термін чистий віртуальний відноситься до віртуальних функцій, які потрібно реалізувати підкласом і не були реалізовані базовим класом. Ви позначаєте метод як чистий віртуальний, використовуючи віртуальне ключове слово та додаючи a = 0 в кінці декларації методу.

Отже, якщо ви хочете зробити Shape :: initShape чистим віртуальним, ви зробили б наступне:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

Додаючи чистий віртуальний метод до свого класу, ви робите клас абстрактним базовим класом, що дуже зручно для відділення інтерфейсів від реалізації.


1
Щодо "віртуальних функцій, які повинен реалізовувати підклас", - це не зовсім вірно, але підклас також абстрактний, якщо їх немає. І абстрактні класи не можуть бути примірниками. Крім того, "неможливо реалізувати базовий клас" здається оманливим; Я б припустив, що "не було" було б краще, оскільки немає обмежень модифікацій коду для додавання реалізації в базовий клас.
NVRAM

2
І "функція getName не може бути реалізована підкласом" не зовсім правильно. Підкласи можуть реалізувати метод (з однаковою або різною підписом), але ця реалізація не перекриє метод. Ви можете реалізувати Circle як підклас та реалізувати "std :: string Circle :: getName ()" - тоді ви можете викликати будь-який метод для екземпляра Circle. Але якщо використовується через покажчик Shape або посилання, компілятор буде викликати Shape :: getName ().
NVRAM

1
Хороші бали на обох фронтах. Я намагався осторонь обговорювати особливі випадки для цього прикладу, я змінив відповідь, щоб бути більш прощальним. Дякую!
Нік Хаддад

@NickHaddad Стара тема, але цікаво, чому ви назвали свою змінну m_name. Що m_означає?
Tqn

1
@Tqn припускаючи, що NickHaddad дотримувався конвенцій, m_name - це умовна умова, яка зазвичай називається угорською нотацією. M означає член структури / класу, ціле число.
Кеткомп

16

"Віртуальний" означає, що метод може бути замінений у підкласах, але має безпосередньо дзвінок в базовий клас. "Чистий віртуальний" означає, що це віртуальний метод без прямої дзвінки. Такий метод повинен бути замінений хоча б один раз в ієрархії спадкування - якщо в класі є якісь нереалізовані віртуальні методи, об'єкти цього класу не можуть бути побудовані, і компіляція завершиться невдачею.

@quark вказує, що чисто-віртуальні методи можуть мати реалізацію, але оскільки чисто-віртуальні методи повинні бути відмінені, реалізація за замовчуванням не може бути безпосередньо викликана. Ось приклад чисто-віртуального методу із замовчуванням:

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

Відповідно до коментарів, компіляція залежить від того, чи не буде компіляція. Принаймні, у GCC 4.3.3 він не компілює:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

Вихід:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()

він повинен бути відмінений, якщо ви хочете створити екземпляр класу. Якщо ви не створите жодних екземплярів, код складеться чудово.
Глен

1
компіляція не провалиться. Якщо немає реалізації (чистого) віртуального методу, то цей клас / об'єкт не може бути ініційований. Він може не СПІЛКУВАТИ, але він буде компілювати.
Тім

@Glen, @tim: на якому компіляторі? Коли я намагаюся скласти програму, яка будує абстрактний клас, вона не компілюється.
Джон Міллікін

@John Компіляція не вдасться, лише якщо ви спробуєте інстанціювати екземпляр класу, який містить PVF. Звичайно, ви можете інстанціювати вказівні або опорні значення для таких класів.

5
Крім того, Джон, наступне не зовсім правильно: "" Чистий віртуальний "означає, що це віртуальний метод без реалізації." Чисті віртуальні методи можуть мати реалізацію. Але ви не можете їх безпосередньо зателефонувати: вам потрібно переосмислити та використати реалізацію базового класу з підкласу. Це дозволяє надати частину реалізації за замовчуванням. Це не звичайна техніка.
кварк

9

Як працює віртуальне ключове слово?

Припустимо, що Людина є базовим класом, індійський походить від людини.

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

Оголошення do_work () віртуальним просто означає: який виклик do_work () буде визначено ТІЛЬКИ під час виконання.

Припустимо, я

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

Якщо віртуальний не використовується, те саме визначається статично або статично пов'язане компілятором, залежно від того, який об'єкт викликає. Отже, якщо об’єкт Людини викликає do_work (), робота людини () називається НАДІЙСЬКІ, ЩО ВИНАЄТЬСЯ НА ІНДІЙСЬКИЙ ОБ'ЄКТ

Я вважаю, що відповідь, що голосує вгорі, вводить в оману - будь-який метод, незалежно від того, віртуальний може мати переохоплену реалізацію у похідному класі При конкретному посиланні на C ++ правильною різницею є час виконання (коли використовується віртуальний) прив'язка та час компіляції (коли віртуальний не використовується, але метод переосмислений і базовий покажчик вказується на похідний об'єкт) прив'язка пов'язаних функцій.

Здається, є ще один оманливий коментар, який говорить:

"Джастін," чистий віртуальний "- це лише термін (не ключове слово, див. Мою відповідь нижче), який використовується для означання" ця функція не може бути реалізована базовим класом ".

ЦЕ НЕПРАВИЛЬНО! Чисто віртуальні функції також можуть мати тіло і МОЖУТЬ ВПРОВАДЖУВАТИ! Правда полягає в тому, що чисту віртуальну функцію абстрактного класу можна назвати статично! Два дуже хороших автори - Б'ярн Струструп і Стен Ліппман .... тому що вони написали мову.


2
На жаль, як тільки відповідь почне отримуватись, всі інші будуть ігноровані. Навіть вони могли бути кращими.
LtWorf

3

Віртуальна функція - це функція-член, яка оголошується в базовому класі і переосмислюється похідним класом. Віртуальна функція є ієрархічною в порядку спадкування. Коли похідний клас не перекриває віртуальну функцію, використовується функція, визначена в її базовому класі.

Чиста віртуальна функція - це та функція, яка не містить визначення відносно базового класу. Він не має реалізації в базовому класі. Будь-який похідний клас повинен перекрити цю функцію.


2

Simula, C ++ і C #, які використовують статичний метод прив'язки за замовчуванням, програміст може вказати, що конкретні методи повинні використовувати динамічне прив'язування, позначаючи їх як віртуальні. Динамічний метод прив'язки є основним для об'єктно-орієнтованого програмування.

Об'єктно-орієнтоване програмування вимагає трьох основних понять: інкапсуляція, успадкування та прив'язка динамічного методу.

Інкапсуляція дозволяє приховувати деталі реалізації абстракції за простим інтерфейсом.

Спадкування дозволяє нову абстракцію визначати як розширення або уточнення деякої існуючої абстракції, отримуючи деякі або всі її характеристики автоматично.

Динамічний метод прив'язки дозволяє новій абстракції відображати свою нову поведінку, навіть коли вона використовується в контексті, який очікує стару абстракцію.


1

Віртуальними методами МОЖЕ бути замінено шляхом отримання класів, але потрібна реалізація в базовому класі (той, який буде переосмислений)

Чисті віртуальні методи не мають реалізації базового класу. Їх потрібно визначити за похідними класами. (Отже, технічно переоцінений не є правильним терміном, тому що не можна нічого переосмислювати).

Віртуальна відповідає поведінці Java за замовчуванням, коли похідний клас замінює метод базового класу.

Чисті віртуальні методи відповідають поведінці абстрактних методів у межах абстрактних класів. І клас, який містить лише чисті віртуальні методи та константи, був би cpp-кулон інтерфейсу.


0

Чиста віртуальна функція

спробуйте цей код

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

У класі elseClass видаліть функцію sayHellow та запустіть код. ви отримаєте помилку! Тому що, коли клас містить чисту віртуальну функцію, жоден об'єкт не може бути створений з цього класу, і він успадковується, то його похідний клас повинен реалізувати цю функцію.

Віртуальна функція

спробуйте інший код

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

Тут функція sayHellow позначена як віртуальна в базовому класі. Скажіть компілятор, який спробує виконати пошук функції у похідному класі та реалізувати функцію. Якщо не знайдено, то виконайте базовий. Дякую


Ха-ха, мені знадобилося довгих 30 секунд, щоб зрозуміти, що тут не так ... HelloW :)
Хан

0

"Віртуальна функція або віртуальний метод - це функція або метод, поведінку якого можна замінити в межах спадкового класу функцією з однаковою підписом" - wikipedia

Це не гарне пояснення для віртуальних функцій. Тому що, навіть якщо член не є віртуальним, класи успадкування можуть його перекрити. Ви можете спробувати і самі це побачити.

Різниця проявляється, коли функція приймає базовий клас як параметр. Коли ви вводите клас успадкування як вхідний, ця функція використовує реалізацію переопределеної функції базового класу. Однак якщо ця функція віртуальна, вона використовує функцію, що реалізована у похідному класі.


0
  • Віртуальні функції повинні мати визначення в базовому класі, а також у похідному класі, але це не обов'язково, наприклад функція ToString () або toString () є віртуальним, щоб ви могли забезпечити власну реалізацію, замінивши її у визначених користувачем класах.

  • Віртуальні функції оголошуються та визначаються у звичайному класі.

  • Чиста віртуальна функція повинна бути оголошена закінченням "= 0", і вона може бути оголошена лише в абстрактному класі.

  • Абстрактний клас, що має чисті віртуальні функції, не може мати визначення цих чистих віртуальних функцій, тому це означає, що реалізація повинна забезпечуватися в класах, що походять від цього абстрактного класу.


Ця ж примітка, що і до @rashedcs: Дійсно, віртуальна функція може мати своє визначення ...
Jarek C
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.