2010-01-31 3 views
81

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

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

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

Я думаю, что вопрос обратный. Причина создания чего-то виртуального всегда одинакова: позволить производным классам переопределять его. Поэтому вопрос должен быть: в чем преимущество частного виртуального метода? На что ответ: сделайте все закрытым по умолчанию. :-) – ShreevatsaR

+0

@ShreevatsaR Но вы даже не ответили на свой вопрос ...... – Spencer

ответ

92

Herb Sutter имеет очень хорошо объяснил это here.

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

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

+0

Как вы могли догадаться из моего ответа, я думаю, что руководство № 3 Саттера скорее вытаскивает из окна окно №2. – Spencer

54

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

(Противоположность Herb Sutter цитирует Prasoon Saurav в своем ответе, на C++ FAQ Lite recommends against private virtuals, в основном потому, что часто сбивает с толку людей.)

+32

Похоже, что C++ FAQ Lite с тех пор изменил свою рекомендацию: «Часто задаваемые вопросы по C++, рекомендованные ранее с использованием виртуальных виртуальных машин, а не частных виртуальных машин. Однако частный виртуальный подход теперь достаточно распространен, что путаница новичков вызывает меньшую озабоченность._ " –

+2

Путаница экспертов, однако, остается предметом беспокойства. Ни один из четырех специалистов C++, сидящих рядом со мной, не знал о частных виртуальных машинах. – Newtonx

2

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

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

Что касается аргумента, что вы не должны иметь общедоступные виртуальные методы, я говорю BS. Пользователи могут ненадлежащим образом переопределять частные виртуальные машины так же легко, как и обычные, ведь они все-таки определяют новые классы. Если публикация не должна изменять данный API, не делайте ее виртуальной AT ALL в общедоступных объектах.

4

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

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

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

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

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

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

7

Несмотря на все призывы объявить виртуальный член частным, аргумент просто не содержит воды. Часто переопределение производного класса виртуальной функции должно вызывать версию базового класса. Он не может, если он объявлен 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 */ } 
... 

Таким образом, руководство Herb Sutter № 3 ... Но лошадь все равно выходит из сарая.

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

Пользователи, которые плохого поведения нарушают это доверие (например, помечены как «невежественные», не утруждая себя чтением вашей документации), сами виноваты.

Обновление: У меня была некоторая обратная связь, в которой утверждается, что вы можете «объединить» реализации виртуальных функций таким образом, используя частные виртуальные функции. Если это так, я бы хотел это увидеть.

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

Если комитет C++ расслабился «на частном», чтобы разрешить этот конкретный доступ, я был бы для частных виртуальных функций. В его нынешнем виде нам по-прежнему советуют запирать дверь сарая после того, как лошадь украдена.

+0

Я считаю ваш аргумент недействительным. Вы как разработчик API должны стремиться к интерфейсу, который ** hard ** использовать неправильно и не устанавливать другого разработчика для своих собственных ошибок. То, что вы хотите сделать в вашем примере, может быть реализовано только с помощью частных виртуальных методов. – sigy

+0

@sigy Затем вам просто нужно будет опубликовать ответ, показывающий, как функция производного класса может вызвать функцию частного базового класса. – Spencer

+0

Это не то, что я говорил. Но вы можете реструктурировать свой код для достижения такого же эффекта без необходимости вызова функции частного базового класса – sigy