C++ 私有和保护的虚方法

11

看起来将虚方法声明为私有可以很好地分离以下两种客户端的接口:

  1. 实例化对象并调用该方法的客户端
  2. 从该类派生并可能想要重写该方法的客户端

简单地说,第一个客户端不需要知道一个方法是否是虚方法。他将调用基类公共非虚方法,而该方法将调用私有虚方法。例如,请参见下面的代码。

现在,在虚方法需要超级传递其基类相应虚方法的情况下,比如说一个保存方法——它必须通过继承链中的所有虚方法才能保存对应于每个派生级别的数据——我们别无选择,只能使用保护虚方法。除非有一种方法可以保证在不使用超级消息传递的情况下在所有派生级别上保存数据(我不知道这种方法)。

我想知道上述推理是否正确。

确保使用滚动条查看整个代码。

#include <iostream>
using namespace std;

class A {

    string data;    

protected:

    virtual void SaveData()= 0;

public:

    A():data("Data of A"){}

    void Save(){
        cout << data << endl;        
        SaveData();
    }
};

class B : public A {

    string data;

protected:

    virtual void SaveData() { cout << data << endl;}

public:

    B():data("Data of B") {}

};

class C : public B {

    string data;
protected:

    virtual void SaveData() {
        B::SaveData();
        cout << data << endl;
    }

public:

    C():data("Data of C") {}
};


int main(int argc, const char * argv[])
{
    C c;
    c.Save();

    return 0;
}
3个回答

12

如果您需要调用另一个类的SaveData,则该类需要从该类访问,因此应为publicprotected


7
您说得很对:
  • NVI(非虚接口)要求virtual方法不是public
  • 调用基类方法要求它不是private

因此,在C++03中,protected是显而易见的解决方案。不幸的是,这意味着您必须相信派生类开发人员不会忘记调用 "super"。


在C++11中,您可以使用final来防止派生类覆盖virtual方法; 这意味着您被迫引入一个新的钩子,例如:

class Base {
public:
    void save() {
        // do something
        this->saveImpl();
        // do something
    }
private:
    virtual void saveImpl() {}
};

class Child: public Base {
private:
     virtual void saveImpl() final {
         // do something
         this->saveImpl2();
         // do something
     }
     virtual void saveImpl2() {}
};

当然,每次都要想出一个新的名字是有点麻烦的......但至少你可以保证 Child::saveImpl 会被调用,因为它的子类都无法覆盖它。

为什么NVI要求虚函数是非公共的? - Rich
@Rich:在C++中,当派生类覆盖一个方法时,它完全替换了它;NVI的存在是为了确保基类保留一定程度的控制(例如,获取锁,检查不变量等),这与虚方法不兼容。更多信息请参见https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface。 - Matthieu M.
谢谢提供链接,我认为我理解了这个成语背后的道德。只是我不太明白当你说NVI 需要虚方法不是public时的含义。除了将客户端(public)接口与实现(非public)接口分开的指导方针外,我看不出为什么公共虚方法会阻止NVI的工作。在上面的例子中声明saveImpl()为public是否会造成灾难? - Rich
@Rich:saveImpl被声明为final(因此不再是虚函数),所以不需要:) - Matthieu M.
@MatthieuM。哦,我错过了final部分。但是你的解决方案特别适用于OP想要的内容,即在每个派生级别上强制传播和处理。在传统的NVI习语中,任何派生类都可以覆盖saveImpl,为什么saveImpl不能是公共的,除非它违反了面向对象原则,即将接口与实现分离。 - Rich
@Rich:实际上,我漏掉了一些东西。将“saveImpl”设置为私有的目的是为了确保它只能通过“save”来调用。想象一下,“save”正在获取锁,而在不获取该锁的情况下调用“saveImpl”可能是灾难性的! - Matthieu M.

2
很难理解您在问什么,但是从例子中看,您不需要将方法设为protected,实际上可以设置为private。有关细节问题,请参阅此文章:What is the point of a private pure virtual function?
只要您不从派生类(或外部类)调用私有成员,就可以了。重写private成员也可以。听起来很淘气和不对,因为你可以重写父类的私有成员,但在c ++ 中这样做是被允许的。
以下内容应该是可以的:
#include <iostream>
using namespace std;

class A {

    string data;    

private:

    virtual void SaveData()= 0;

public:

    A():data("Data of A"){}

    void Save(){
        cout << data << endl;        
        SaveData();
    }
};

class B : public A {

    string data;

private:

    virtual void SaveData() { cout << data << endl;}

public:

    B():data("Data of B") {}

};

这是覆盖而不是重载 - R. Martinho Fernandes
2
然而,这并没有解决调用基类的SaveData成员函数的问题,我认为这才是最初的问题所在! - Mats Petersson
@MatsPetersson 看起来问题已经被编辑过了。就像我说的,很难知道在问什么。 - thang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接