Приватний віртуальний метод в C ++


125

Яка перевага зробити приватний метод віртуальним у C ++?

Я помітив це у проекті C ++ з відкритим кодом:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};

9
Я думаю, що питання є зворотним. Причина зробити щось віртуальне завжди одна: дозволити похідним класам переосмислити це. Тож має бути питання: яка перевага зробити віртуальний метод приватним? На що відповідь: зробити все приватним за замовчуванням. :-)
ShreevatsaR

1
@ShreevatsaR Але ви навіть не відповіли на власне запитання ......
Спенсер

@ShreevatsaR Я подумав, що ти маєш на увазі зворотне по-іншому: Яка перевага в тому, щоб зробити віртуальний метод не приватним?
Пітер - Відновити Моніку

Відповіді:


116

Герб Саттер дуже гарно це пояснив тут .

Вказівка ​​№2: Віддайте перевагу приватним віртуальним функціям.

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


Як ви можете здогадатися з моєї відповіді, я думаю, що настанова Саттера №3 швидше висуває керівництво №2 у вікно.
Спенсер

66

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

(На противагу Герб Саттер, процитований Прасоном Соравом у своїй відповіді, література C ++ FAQ рекомендує проти приватних віртуалів , здебільшого тому, що це часто бентежить людей.)


41
Здається, що C ++ FAQ Lite згодом змінив свою рекомендацію: " Раніше FAQ + та C ++ раніше рекомендували використовувати захищені віртуальні, а не приватні віртуальні. Однак приватний віртуальний підхід зараз досить поширений, що плутанина новачків викликає менше занепокоєння ".
Зак Людина

19
Плутанина експертів, однак, залишається занепокоєнням. Жоден із чотирьох професіоналів C ++, які сиділи поруч зі мною, не знав про приватні віртуалі.
Ньютонкс

12

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

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

Ви повинні оголосити метод базового класу protected.

Тоді вам доведеться сприймати некрасиві докази через коментар, що метод слід перекрити, але не викликати.

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

Таким чином, керівництво №3 Герба Саттера ... Але кінь все одно вийшов із сараю.

Коли ви декларуєте щось, protectedви неявно довіряєте письменнику будь-якого похідного класу, щоб зрозуміти та належним чином використовувати захищені інтернали, саме так, як friendдекларація передбачає більш глибоку довіру до privateчленів.

Користувачі, які мають погану поведінку в порушенні цього довіри (наприклад, позначений як "безглуздий", не намагаючись читати вашу документацію), винні лише у цьому.

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

Компілятори C ++, які я використовую, точно не дозволяють реалізації похідного класу викликати реалізацію приватного базового класу.

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


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

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

3
У своєму прикладі ви хочете розширити поведінку set_data. Інструкції m_data = ndata;і, cleanup();таким чином, можна вважати інваріантом, який повинен бути виконаний для всіх реалізацій. Тому робіть cleanup()невіртуальне та приватне. Додайте виклик до іншого приватного методу, який є віртуальним та точкою розширення вашого класу. Тепер вже не потрібно, щоб ваші похідні класи більше не називали базу cleanup(), ваш код залишається чистим, а ваш інтерфейс важко неправильно використовувати.
підпис

2
@sigy Це просто переміщує ворота. Потрібно дивитись за межі злочинного прикладу. Коли є інші нащадки, яким потрібно викликати всі cleanup()s в ланцюзі, аргумент розпадається. Або ви рекомендуєте додаткову віртуальну функцію для кожного нащадка ланцюга? Ick. Навіть Герб Саттер дозволяв захищати віртуальні функції як лазівку в своєму керівництві №3. У всякому разі, без якогось фактичного коду ти мене ніколи не переконуєш.
Спенсер

2
Тоді давайте погодимось не погодитися;)
підпишіться

9

Я вперше зіткнувся з цією концепцією, читаючи «Ефективний C ++» Скотта Майєрса, пункт 35: Розгляньте альтернативи віртуальним функціям. Я хотів посилатися на Скотта Майєра на інших, які можуть зацікавити.

Це частина шаблону методу шаблону через ідіому невіртуального інтерфейсу : загальнодоступні методи не є віртуальними; скоріше, вони переносять віртуальні виклики методу, які є приватними. Потім базовий клас може запускати логіку до і після виклику приватної віртуальної функції:

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

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

  • Навіщо робити віртуальну функцію private? Найкраща причина полягає в тому, що ми вже запропонували publicметод облицювання.
  • Чому б просто не зробити protectedтак, щоб я міг використовувати метод для інших цікавих речей? Я припускаю, що це завжди залежатиме від вашого дизайну та того, як ви вважаєте, що базовий клас підходить. Я б стверджував, що похідний виробник класів повинен орієнтуватися на реалізацію необхідної логіки; про все інше вже подбали. Крім того, тут справа інкапсуляції.

З точки зору C ++, цілком законно перекрити приватний віртуальний метод, навіть якщо ви не зможете викликати його зі свого класу. Це підтримує описаний вище дизайн.


3

Я використовую їх, щоб дозволити похідним класам "заповнювати пробіли" для базового класу, не піддаючи такий отвір кінцевим користувачам. Наприклад, у мене є високостабільні об'єкти, що походять із загальної бази, яка може реалізувати лише 2/3 загальної машини машини (похідні класи забезпечують решту 1/3 залежно від аргументу шаблону, а база не може бути шаблоном для інші причини).

Я ПОТРІБНО мати загальний базовий клас для того, щоб багато публічних API працювали коректно (я використовую різні шаблони), але я не можу випускати цей об'єкт у дику природу. Гірше, якщо я залишаю кратери в державній машині - у вигляді чисто віртуальної функції - в будь-якому місці, окрім "Приватного", я дозволяю розумному або неосвіченому користувачеві, який походить з одного з його дочірніх класів, переосмислювати методи, до яких користувачі ніколи не повинні торкатися. Отже, я поклав «мізки» державної машини на ПРИВАТНІ віртуальні функції. Тоді безпосередні діти базового класу заповнюють пробіли на своїх NON-віртуальних перекриттях, і користувачі можуть безпечно користуватися отриманими об'єктами або створювати власні подальші похідні класи, не переживаючи, щоб зіпсувати стан машини.

Щодо аргументу про те, що ви не повинні мати публічних віртуальних методів, я кажу, що BS. Користувачі можуть неправильно перекривати приватні віртуальні так само легко, як і загальнодоступні - вони визначають нові класи. Якщо публіка не повинна змінювати даний API, не робіть його віртуальним ВСЕ у загальнодоступних об'єктах.

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