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


139

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

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

Answers:


209

主题中的问题表明了一个非常普遍的困惑。混乱很常见,C ++ FAQ长期以来一直提倡不使用私有虚拟机,因为混乱似乎是一件坏事。

因此,首先要消除混乱:是的,可以在派生类中重写私有虚拟函数。派生类的方法不能从基类调用虚函数,但是它们可以为其提供自己的实现。根据Herb Sutter的说法,在基类中具有公共非虚拟接口,并可以在派生类中自定义私有实现,可以更好地“将接口的规范与实现的可定制行为的规范分开”。您可以在他的文章“ Virtuality”中了解更多信息。

但是,在我看来,您提供的代码中还有一件有趣的事情,值得更多注意。公用接口由一组重载的非虚拟函数组成,这些函数称为非公用,非重载的虚拟函数。像在C ++世界中一样,它是一个习惯用法,它有一个名称,并且当然有用。名字是(惊喜,惊喜!)

“公共重载的非虚拟机呼叫保护的非重载的虚拟机”

它有助于正确管理隐藏规则。您可以在此处阅读有关内容的更多信息,但我会尽快对其进行解释。

想象一下,Engine该类的虚函数也是其接口,并且它是一组重载函数,它们不是纯虚函数。如果它们是纯虚拟的,则仍然可能遇到相同的问题,如下所述,但在类层次结构中较低。

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

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

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*/}
};

为什么虚拟功能必须是私有的?可以公开吗?
丰富

我想知道赫伯·萨特(Herb Sutter)在他的“虚拟性”文章中给出的指导原则今天是否仍然有效?
nurabha '16

@Rich您可以,但是通过使其不公开,您可以更清楚地传达其意图。首先,如果您坚持将接口设为公开而将实现设为非公开,则表示关注点分离。其次,如果您希望继承的类能够调用基本实现,则可以将它们声明为protected;如果只希望他们提供自己的实现而不调用基本实现,则可以将它们设为私有。
丹丹

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



4

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

它将能够覆盖那些私有功能。例如,以下人为设计的示例作品(EDIT:将派生类方法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设计模式。这种设计模式允许更改基类中算法的行为,而无需更改基类中的代码。上面的通过基类指针调用基类方法的代码是Template Method模式的简单示例。


我知道了,但是如果派生类仍然具有某种访问权限,为什么还要麻烦地将它们设为私有?
BeeBand 2010年

@BeeBand:用户可以访问公共派生类的虚拟方法重写,但不能访问基类的方法。在这种情况下,派生类作者也可以使虚方法重写私有。实际上,我将在上面的示例代码中进行更改以强调这一点。无论哪种方式,它们始终可以公开继承并覆盖私有基类虚拟方法,但它们仍然只能访问自己的派生类虚拟方法。请注意,我是在覆盖和访问/调用之间进行区分。
无效

因为你错了。类之间的继承知名度EngineDerivedEngine无关什么DerivedEngine可以或不可以重写(或访问,对于这个问题)。
威廉希尔

@wilhelmtell:叹气 你当然是对的。我将相应地更新答案。
无效

3

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

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


编辑模板方法模式中有效使用了私有虚拟方法。派生类可以覆盖私有虚拟方法,但是派生类不能调用其基类私有虚拟方法(在您的示例中为SetStateBoolSetStateInt)。只有基类可以有效地调用其私有虚拟方法(仅当派生类需要调用虚拟函数的基本实现时,才应将虚拟函数设置为protected)。

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


2
@Gentleman ...嗯,向下滚动到Colin D Bennett的评论。他似乎认为“私有虚拟函数可以被派生类覆盖,但只能在基类内部调用”。@Michael Goldshteyn也这样认为。
BeeBand 2010年

我猜您已经忘记了一个原则,即基于私有的类不能被其派生类看到。这就是OOP规则,它适用于所有OOP语言。为了使派生类实现其基类私有虚拟方法,它必须是friend基类的。Qt在实施XML DOM文档模型时采用了相同的方法。
Buhake Sindi

@绅士:不,我没有忘记。我在评论中打错了字。我应该写的是“可以覆盖基类方法”,而不是“访问基类方法”。派生类当然可以覆盖私有虚拟基类方法,即使它不能访问该基类方法。您指出的DevX.com文章不正确(公共继承)。试试我的答案中的代码。尽管有私有虚拟基类方法,但派生类仍可以覆盖它。让我们不要将覆盖私有虚拟基类方法的能力与调用它的能力相混淆。
无效

@绅士:@wilhelmtell指出了我的答案/评论中的错误。我关于继承会影响基类方法的派生类可访问性的说法已经断言。我已经删除了您的回答中令人反感的评论。
无效

@Void,我看到派生类可以重写其基类的私有虚拟方法,但不能使用它。因此,这本质上是模板方法模式。
Buhake Sindi

0

TL; DR答案:

您可以将其视为另一种封装级别-在protectedprivate之间:您不能从子类中调用它,但是可以覆盖它。

在实现模板方法设计模式时很有用。您可以使用protected,但是将privatevirtual一起使用会因为更好的封装而被认为是更好的选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.