私有纯虚函数有什么意义?

时间:2010-10-19 16:00:30

标签: c++ oop inheritance non-virtual-interface

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

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类或从它派生的类必须为这些纯虚函数提供实现。但我不认为派生类可以访问这些私有函数以重新实现它们 - 那么为什么要将它们变为虚拟?

6 个答案:

答案 0 :(得分:194)

该主题中的问题表明存在相当常见的混淆。混淆是很常见的,C++ FAQ提倡反对使用私人虚拟机很长一段时间,因为混乱似乎是一件坏事。

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

在我看来,你提出的代码中还有一个有趣的东西,值得我更多关注。公共接口由一组重载的非虚函数组成,这些函数调用非公共的,非重载的虚函数。像往常一样在C ++世界中它是一个成语,它有一个名字,当然它是有用的。这个名字是(惊讶,惊讶!)

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

properly manage the hiding rule有帮助。您可以阅读更多相关信息here,但我会尽快解释一下。

想象一下,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 ),将true转换为int

如果界面不是虚拟的并且虚拟实现是非公开的,就像在你的例子中那样,派生类的作者只需要考虑一个问题,可以简单地写一下

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};

答案 1 :(得分:40)

私有纯虚拟功能是非虚拟接口成语的基础(好吧,它并非绝对总是虚拟,但仍然虚拟那里)。当然,这也用于其他事情,但我发现这最有用(:在两个词中:在公共函数中,你可以在开头放一些常见的东西(比如日志,统计等)在函数的最后,然后,“在中间”调用这个私有虚函数,对于特定的派生类将是不同的。如:

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

答案 2 :(得分:17)

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

答案 3 :(得分:4)

编辑:澄清了有关覆盖能力和访问/调用能力的陈述。

它将能够覆盖这些私有函数。例如,以下设计的示例有效(编辑:将派生类方法设为私有,并在main()中删除派生类方法调用,以更好地演示正在使用的设计模式的意图。):

#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方法(如代码中的方法)通常用于实现Template Method design pattern。该设计模式允许人们在不改变基类中的代码的情况下改变基类中算法的行为。通过基类指针调用基类方法的上述代码是模板方法模式的一个简单示例。

答案 4 :(得分:2)

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

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


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

有关Virtuality的有趣文章。

答案 5 :(得分:0)

TL; DR答案:

您可以将其视为另一种封装级别-在受保护的私有之间:您不能从子类中调用它,但是可以覆盖它。 / p>

在实现Template Method设计模式时很有用。您可以使用受保护的,但由于封装效果更好,因此私有虚拟一起被认为是更好的选择。