148

我在头文件中遇到了以下代码:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

对我来说,这意味着无论是Engine类还是从它派生的类,都必须为那些纯虚函数提供实现。但是我不认为派生类可以访问这些私有函数来重新实现它们——那么为什么要让它们成为虚拟的呢?

4

6 回答 6

214

该主题中的问题提出了一个非常常见的混淆。这种混淆很常见,C++ FAQ长期以来一直反对使用私有虚拟,因为混淆似乎是一件坏事。

所以首先要摆脱混淆:是的,私有虚函数可以在派生类中被覆盖。派生类的方法不能从基类调用虚函数,但可以为它们提供自己的实现。根据 Herb Sutter 的说法,在基类中具有公共的非虚拟接口和可以在派生类中定制的私有实现,可以更好地“将接口规范与实现的可定制行为规范分离”。您可以在他的文章“虚拟性”中阅读更多相关信息。

然而,在我看来,您提供的代码中还有一件更有趣的事情值得更多关注。公共接口由一组重载的非虚函数组成,这些函数调用非公共、非重载的虚函数。像往常一样,在 C++ 世界中,它是一个习语,它有一个名字,当然它也很有用。名字是(惊喜,惊喜!)

“公共重载非虚拟调用受保护非重载虚拟”

它有助于正确管理隐藏规则你可以在这里阅读更多关于它的信息,但我会尽快解释它。

想象一下,Engine类的虚函数也是它的接口,它是一组非纯虚的重载函数。如果它们是纯虚拟的,仍然会遇到同样的问题,如下所述,但在类层次结构中较低。

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

现在让我们假设您要创建一个派生类,并且您只需要为该方法提供一个新的实现,它需要两个整数作为参数。

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

如果您忘记将 using 声明放在派生类中(或重新定义第二个重载),您可能会在下面的场景中遇到麻烦。

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

如果您没有阻止隐藏Engine成员,则声明:

myV8->SetState(5, true);

void SetState( int var, int val )将从派生类调用,转换trueint.

如果接口不是虚拟的并且虚拟实现是非公共的,就像在你的例子中,派生类的作者要考虑的问题就少了,可以简单地写

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
于 2010-10-20T13:48:56.097 回答
43

私有纯虚函数是非虚接口习语的基础(好吧,它并不总是虚函数,但仍然是虚函数)。当然,这也用于其他事情,但我发现它最有用(:一句话:在公共函数中,您可以将一些常见的东西(例如日志记录、统计信息等)放在开头,然后在函数的末尾,然后在“中间”调用这个私有虚函数,这对于特定的派生类会有所不同。像:

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

纯虚拟- 只是强制派生类实现它。

编辑:有关此的更多信息:Wikipedia::NVI-idiom

于 2010-10-19T16:12:35.123 回答
17

好吧,首先,这将允许派生类实现基类(包含纯虚函数声明)可以调用的函数。

于 2010-10-19T16:04:34.020 回答
4

EDIT: Clarified statements about ability to override and ability to access/invoke.

It will be able to override those private functions. For example, the following contrived example works (EDIT: made derived class method private, and drop the derived class method invocation in main() to better demonstrate the intent of design pattern in use.):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtual methods in a base class like the ones in your code are typically used to implement the Template Method design pattern. That design pattern allows one to change the behavior of an algorithm in the base class without changing the code in the base class. The above code where the base class methods are invoked through a base class pointer is a simple example of the Template Method pattern.

于 2010-10-19T16:40:43.983 回答
2

私有虚拟方法用于限制可以覆盖给定函数的派生类的数量。必须覆盖私有虚拟方法的派生类必须是基类的朋友。

可以找到DevX.com的简要说明。


编辑Template Method Pattern中有效地使用了私有虚拟方法。派生类可以覆盖私有虚拟方法,但派生类不能调用它的基类私有虚拟方法(在您的示例中,SetStateBoolSetStateInt)。只有基类才能有效地调用其私有虚方法(只有派生类需要调用虚函数的基实现时,才使虚函数受保护)。

可以找到一篇关于Virtuality的有趣文章。

于 2010-10-19T16:07:05.567 回答
1

TL;DR 答案:

您可以将其视为另一层封装 - 介于protectedprivate之间:您不能从子类调用它,但可以覆盖它。

在实现模板方法设计模式时很有用。您可以使用protected,但由于更好的封装, privatevirtual可能被认为是更好的选择。

于 2019-08-13T13:13:24.590 回答