关于c ++:私有纯虚函数有什么意义?

What is the point of a private pure virtual function?

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

1
2
3
4
5
6
7
8
9
10
11
12
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类还是从它派生的类,都必须为这些纯虚拟函数提供实现。但我不认为派生类可以访问这些私有函数来重新实现它们——那么为什么要使它们成为虚拟的呢?


这个主题中的问题暗示了一个相当普遍的困惑。这种混淆是常见的,C++FAQ提倡使用私人虚拟,长期以来,因为混淆似乎是一件坏事。

因此,为了首先摆脱混乱:是的,私有虚拟函数可以在派生类中被重写。派生类的方法不能从基类调用虚拟函数,但它们可以为它们提供自己的实现。根据Herb Sutter的说法,在基类中有公共的非虚拟接口,在派生类中有一个可以定制的私有实现,允许更好地"将接口规范与实现的可定制行为规范分离开来"。你可以在他的文章"虚拟性"中了解更多。

然而,在我看来,在您所提供的代码中还有一个更有趣的东西需要更多的关注。公共接口由一组重载的非虚拟函数组成,这些函数调用非公共的非重载虚拟函数。与C++一样,它是一个成语,它有一个名字,当然它是有用的。名字是(惊喜,惊喜!)

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

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

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

1
2
3
4
5
6
class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

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

1
2
3
4
5
6
7
8
9
10
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声明放在派生类中(或重新定义第二个重载),在下面的场景中可能会遇到麻烦。

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

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

1
myV8->SetState(5, true);

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

如果接口不是虚拟的,并且虚拟实现是非公共的,就像在您的ExMaple中一样,派生类的作者有一个较少的问题需要考虑,可以简单地编写

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


私有纯虚拟函数是非虚拟接口习惯用法的基础(好吧,它不是绝对的纯虚拟,但仍然是虚拟的)。当然,这也用于其他事情,但我发现这是最有用的(:用两个词来说:在公共函数中,您可以在函数的开头和结尾放置一些常见的东西(如日志、统计信息等),然后在中间调用这个私有虚拟函数,这对于特定的派生函数来说是不同的。类。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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()
{
    // ..
}

纯虚拟-只要求派生类实现它。

编辑:关于此的详细信息:维基百科::nvi习语


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


编辑:阐明有关重写能力和访问/调用能力的语句。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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);
}

类中的Privatevirtual方法(如代码中的方法)通常用于实现模板方法设计模式。该设计模式允许在不更改基类中的代码的情况下更改基类中算法的行为。上面通过基类指针调用基类方法的代码是模板方法模式的一个简单示例。


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

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

在模板方法模式中,有效地使用了私有虚拟方法。派生类可以重写私有虚拟方法,但派生类不能调用它的基类私有虚拟方法(在您的示例中,SetStateBoolSetStateInt)。只有基类才能有效地调用它的私有虚方法(只有当派生类需要调用虚函数的基实现时,才使虚函数受保护)。

关于虚拟性,可以找到一篇有趣的文章。