Частный виртуальный метод в C ++

125

В чем преимущество создания виртуального частного метода в C ++?

Я заметил это в проекте C ++ с открытым исходным кодом:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};
silverburgh
источник
9
Думаю, вопрос в обратном направлении. Причина создания чего-то виртуального всегда одна и та же: позволить производным классам переопределить это. Возникает вопрос: в чем преимущество приватности виртуального метода? На что ответ: по умолчанию сделать все приватным. :-)
ShreevatsaR 02
1
@ShreevatsaR Но ты даже не ответил на свой вопрос ...
Спенсер
@ShreevatsaR Я думал, вы имеете в виду обратное по-другому: в чем преимущество того, что виртуальный метод не является частным?
Питер - Восстановить Монику

Ответы:

116

Херб Саттер очень хорошо объяснил это здесь .

Правило №2: Предпочитайте делать виртуальные функции приватными.

Это позволяет производным классам переопределять функцию для настройки поведения по мере необходимости, без дальнейшего непосредственного раскрытия виртуальных функций, делая их доступными для вызова производными классами (что было бы возможно, если бы функции были просто защищены). Дело в том, что виртуальные функции существуют для настройки; если их также не нужно вызывать непосредственно из кода производных классов, нет необходимости делать их чем-либо, кроме закрытых

Prasoon Saurav
источник
Как вы можете догадаться из моего ответа, я думаю, что рекомендация № 3 Саттера скорее выталкивает руководство № 2 из окна.
Спенсер
Что, если производному классу необходимо переопределить метод, но вызвать родительский метод изнутри? Это достаточно распространено, что я не могу себе представить, что частные виртуальные машины будут рекомендованы, если они это заблокируют. Есть ли у C ++ такой механизм, как super(...)вызов родительского метода из переопределенной версии, который работает, даже если он частный?
flarn2006
66

Если метод виртуальный, он может быть переопределен производными классами, даже если он частный. При вызове виртуального метода будет вызвана переопределенная версия.

(В отличие от Херба Саттера, которого цитирует Прасун Саурав в своем ответе, C ++ FAQ Lite не рекомендует использовать частные виртуальные машины , в основном потому, что это часто сбивает с толку людей.)

STH
источник
41
Оказывается , что C ++ FAQ Lite с той пор изменила свою рекомендацию: « С ++ FAQ ранее рекомендовала использовать защищенные виртуал , а не частные виртуал Однако частный виртуальный подход теперь распространено настолько , что путаница послушников является меньше беспокойства.. »
Zack Human
19
Однако замешательство экспертов по-прежнему вызывает озабоченность. Ни один из четырех сидящих рядом со мной специалистов по C ++ не знал о частных виртуальных машинах.
Newtonx
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, должны стремиться к интерфейсу, который трудно использовать неправильно, и не настраивать другого разработчика на свои собственные ошибки. То, что вы хотите сделать в своем примере, можно реализовать только с помощью частных виртуальных методов.
sigy
1
Я не это говорил. Но вы можете реструктурировать свой код для достижения того же эффекта без необходимости вызывать частную функцию базового класса
сиги
3
В вашем примере вы хотите расширить поведение set_data. Инструкции m_data = ndata;и cleanup();поэтому могут считаться инвариантом, который должен соблюдаться для всех реализаций. Поэтому сделайте cleanup()не виртуальным и приватным. Добавьте вызов другого частного метода, который является виртуальным и точкой расширения вашего класса. Теперь вашим производным классам cleanup()больше не нужно вызывать base , ваш код остается чистым, а ваш интерфейс трудно использовать неправильно.
sigy
2
@sigy Это просто перемещает стойки ворот. Вам нужно выйти за рамки мнимого примера. Когда есть другие потомки, которым нужно вызвать все cleanup()s в цепочке, аргумент разваливается. Или вы рекомендуете дополнительную виртуальную функцию для каждого потомка в цепочке? Ик. Даже Херб Саттер разрешил защищенные виртуальные функции как лазейку в своем руководстве № 3. В любом случае, без кода вы меня никогда не убедите.
Спенсер
2
Тогда давай соглашаемся не соглашаться;)
sigy
9

Я впервые столкнулся с этой концепцией, когда читал «Эффективный C ++» Скотта Мейерса, совет 35: Рассмотрите альтернативы виртуальным функциям. Я хотел бы сослаться на Скотта Майерса для других, которые могут быть заинтересованы.

Это часть шаблона метода шаблона с помощью идиомы невиртуального интерфейса : общедоступные методы не являются виртуальными; скорее они обертывают вызовы виртуальных методов, которые являются частными. Затем базовый класс может запускать логику до и после вызова частной виртуальной функции:

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

Я думаю, что это очень интересный шаблон проектирования, и я уверен, что вы видите, чем полезен добавленный элемент управления.

  • Зачем делать виртуальную функцию private? Лучшая причина в том, что мы уже предоставили publicметод облицовки.
  • Почему бы просто не сделать protectedтак, чтобы я мог использовать этот метод для других интересных вещей? Я полагаю, это всегда будет зависеть от вашего дизайна и того, как, по вашему мнению, подходит базовый класс. Я бы сказал, что создатель производного класса должен сосредоточиться на реализации необходимой логики; обо всем остальном уже позаботились. Также есть вопрос инкапсуляции.

С точки зрения C ++, полностью законно переопределить частный виртуальный метод, даже если вы не сможете вызвать его из своего класса. Это поддерживает дизайн, описанный выше.

Pooven
источник
3

Я использую их, чтобы позволить производным классам «заполнять пробелы» для базового класса, не открывая такую ​​дыру для конечных пользователей. Например, у меня есть объекты с высоким уровнем состояния, происходящие из общей базы, которая может реализовывать только 2/3 общего конечного автомата (производные классы предоставляют оставшуюся 1/3 в зависимости от аргумента шаблона, а база не может быть шаблоном для другие причины).

МНЕ НУЖНО иметь общий базовый класс, чтобы многие общедоступные API работали правильно (я использую вариативные шаблоны), но я не могу позволить этому объекту выйти в свет. Хуже того, если я оставлю кратеры в конечном автомате - в форме чистых виртуальных функций - где угодно, кроме «Private», я разрешаю умному или невежественному пользователю, производному от одного из его дочерних классов, переопределить методы, к которым пользователи никогда не должны прикасаться. Итак, я вложил "мозги" конечного автомата в ЧАСТНЫЕ виртуальные функции. Затем непосредственные дочерние элементы базового класса заполняют пробелы в своих НЕвиртуальных переопределениях, и пользователи могут безопасно использовать полученные объекты или создавать свои собственные производные классы, не беспокоясь о нарушении работы конечного автомата.

Что касается аргумента, что вам не следует ИМЕТЬ публичные виртуальные методы, я говорю BS. Пользователи могут неправильно переопределять частные виртуальные машины так же легко, как и публичные - в конце концов, они определяют новые классы. Если публика не должна изменять данный API, не делайте его ВООБЩЕ в общедоступных объектах.

Зак Йезек
источник