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


176

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

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

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

Чи вище код ОК?

Яка мета зробити це чистою віртуальною функцією з реалізацією?

Відповіді:


215

Чиста virtualфункція повинна бути реалізована у похідному типі, який буде безпосередньо інстанційним, проте базовий тип все ще може визначити реалізацію. Похідний клас може явно викликати реалізацію базового класу (якщо дозволи дозволу на це дозволяють), використовуючи повнокореневе ім'я (за допомогою виклику A::f()у вашому прикладі - якщо вони A::f()були publicчи protected). Щось на зразок:

class B : public A {

    virtual void f() {
        // class B doesn't have anything special to do for f()
        //  so we'll call A's

        // note that A's declaration of f() would have to be public 
        //  or protected to avoid a compile time problem

        A::f();
    }

};

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

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


1
Ви забули додати, чому це дивує програмістів: це тому, що визначення вбудованого тексту заборонено стандартом. Чисті визначення віртуальних методів повинні бути deported. (або в .inl або .cpp, щоб посилатися на загальні практики іменування файлів).
v.oddou

тож цей метод виклику такий же, як і статичний метод виклику учасника. Якийсь метод класу на Java.
Sany Liew

2
"не використовується" == погана практика? Я шукав саме таку поведінку, намагаючись реалізувати NVI. І NVI здається мені гарною практикою.
Саскія

5
Варто зазначити, що зробити A :: f () чистим - це означає, що B повинен реалізувати f () (інакше B було б абстрактним і невідчутним). І як вказує @MichaelBurr, надання реалізації для A :: f () означає, що B може використовувати її для визначення f ().
безстрашний_фул

2
IIRC, Скотт Мейєр має чудову статтю про використання цього питання в одній зі своїх класичних книг "Більш ефективний C ++"
irsis

75

Щоб було зрозуміло, ви нерозумієте що = 0; після віртуальної функції означає.

= 0 означає, що похідні класи повинні забезпечувати реалізацію, а не те, що базовий клас не може забезпечити реалізацію.

На практиці, коли ви позначаєте віртуальну функцію як чисту (= 0), дуже мало сенсу в наданні визначення, оскільки вона ніколи не буде викликана, якщо хтось явно не зробить це через Base :: Function (...) або якщо Конструктор базового класу викликає питання щодо віртуальної функції.


9
Це неправильно. Якщо ви будете викликати цю чисту віртуальну функцію в конструкторі вашого чистого віртуального класу, буде зроблено чистий віртуальний виклик. У такому випадку вам краще мати реалізацію.
rmn

@rmn, Так, ти маєш рацію щодо віртуальних викликів у конструкторах. Я оновив відповідь. Сподіваємось, всі знають, що цього не роблять. :)
Terry Mahaffey

3
Фактично, здійснення базового чистого виклику від конструктора призводить до поведінки, визначеної реалізацією. У VC ++ це означає збій _purecall.
Офек Шилон

@OfekShilon це правильно - я б спокусився також назвати це невизначеною поведінкою та кандидатом на погану практику / рефакторинг коду (тобто виклик віртуальних методів всередині конструктора). Я думаю, це пов'язано з когерентністю віртуальної таблиці, яка може бути не готовою до маршруту правильної реалізації.
теодрон

1
У конструкторів та деструкторів віртуальні функції не є віртуальними.
Jesper Juhl

20

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


1
Чому я б хотів примусити, якщо є реалізація за замовчуванням? Це звучить як звичайні віртуальні функції. Якщо це була лише звичайна віртуальна функція, я можу або переосмислити, а якщо цього не зробила, тоді буде забезпечена реалізація за замовчуванням (реалізація бази).
StackExchange123

19

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

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

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

Наступний код напрочуд правильний:

class Base {
public:
  virtual ~Base() = 0;
};

Base::~Base() {}

class Derived : public Base {};

int main() { 
  // Base b; -- compile error
  Derived d; 
}

1
Деструктори базового класу завжди називаються віртуальними чи ні, чистими чи ні; з іншими функціями ви не можете гарантувати, що переважаюча віртуальна функція викликає реалізацію базового класу, чи є версія базового класу чиста чи ні.
CB Bailey

1
Цей код неправильний. Ви повинні визначити dtor за межами визначення класу через синтаксичний запит мови.

@Roger: спасибі, що насправді мені допомогло - це код, який я використовував, він чудово компілює під MSVC, але, мабуть, він не був би портативним.
Kornel Kisielewicz


4

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

Скотт Майєрс обговорює це в Ефективному C ++ (2-е видання) Пункт №36. Розрізняйте спадкування інтерфейсу та успадкування реалізації. Номер товару може бути змінений в останній редакції.


4

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

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


2

'Віртуальна пустота foo () = 0;' синтаксис не означає, що ви не можете реалізувати foo () у поточному класі, ви можете. Це також не означає, що потрібно реалізовувати його у похідних класах . Перш ніж ви ляпнете мене, давайте подивимось на Діамантову проблему: (неявний код, пам'ятайте).

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

class B : public virtual A
{
public:
    void foo() { bar(); }
}

class C : public virtual A
{
public:
    void bar();
}

class D : public B, public C
{}

int main(int argc, const char* argv[])
{
    A* obj = new D();
    **obj->foo();**
    return 0;
}

Тепер виклик obj-> foo () призведе до B :: foo (), а потім C :: bar ().

Ви бачите ... чисті віртуальні методи не повинні бути реалізовані у похідних класах (foo () не має реалізації в класі C - компілятор буде компілювати) У C ++ дуже багато лазівки.

Сподіваюся, що я можу допомогти :-)


5
Її не потрібно реалізовувати у ВСІХ похідних класах, але ОБОВ'ЯЗКОВО мати впровадження у всіх похідних класах, які ви маєте намір інстанціювати. Ви не можете створити екземпляр типу об'єкта Cу своєму прикладі. Ви можете створити екземпляр типу об'єкта, Dоскільки він отримує свою реалізацію fooвід B.
YoungJohn

0

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

class Foo
{
   virtual ~Foo() = 0;
   void bar1() {}
   void bar2(int x) {}
   // other methods
};

Foo::~Foo()
{
}

Ця методика робить Fooклас абстрактним і, як результат, неможливо інстанціювати клас безпосередньо. У той же час ви не додали додатковий чистий віртуальний метод, щоб зробити Fooклас абстрактним.

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