了解'protected virtuals'和'public virtuals'之间的比较



问题来自C++问题。

http://www.parashift.com/c++-faq lite/protected-virtuals.html

使用公共重载虚拟机的代码:

class Base {
public:
  virtual void f(int x);    ← may or may not be pure virtual
  virtual void f(double x); ← may or may not be pure virtual
};

通过公共过载非虚拟调用保护的非过载虚拟成语:改进这一点

class Base {
public:
  void f(int x)    { f_int(x); }  ← non-virtual
  void f(double x) { f_dbl(x); }  ← non-virtual
protected:
  virtual void f_int(int);
  virtual void f_dbl(double);
};

作者说:

Public Overloaded Non-Virtuals Call Protected Non-Verbloaded Virtuals习语的思想是将公共重载方法更改为非虚拟方法,并使这些方法成为受保护的非重载虚拟方法。

但我不明白作者对这个成语如何提高风险说了什么:

该习语将正确管理隐藏规则的复杂性打包到基类中(单数)。这意味着派生类(复数)或多或少会自动处理隐藏规则,因此生成这些派生类的各种开发人员几乎可以完全专注于派生类本身的细节——他们不必关心(微妙且经常被误解的)隐藏规则。这大大减少了派生类的编写者破坏隐藏规则的可能性。

为什么这能解决隐藏问题?据我所知,名称隐藏与成员函数是否为"虚拟"无关。如果"base"的派生类重写函数f(),它仍然会隐藏f(int)和f(double),对吗?

从这个习语中,我所能看到的是作者将"base"virtual f()改为非virtual,并将helper函数f_int()、f_dbl()放在"protected virtual"中,就像习语的名称所说的那样。它并没有什么好处,但恰恰相反,它消除了从基类指针/引用动态绑定的可能性。这个成语的真正好处是什么?

更新

科瑞克,你是说这个吗?我不完全理解你回答的第二段。你能举个例子吗?

class base {
public: 
    virtual void f(int x);
    virtual void f(double x);
}
class derived : public base {
public:
    virtual int f(int x); // oops, will hide base::f(int x) AND base::f(double x)
}
base *bp = new base();
base *dp = new derived();
bp->f(int i);    // ok
dp->f(int i);    // surprise!
dp->f(double d); // compile error!

class Base {
public:
    void f(int x)    { f_int(x); }  
    void f(double x) { f_dbl(x); } 
protected:
    virtual void f_int(int);
    virtual void f_dbl(double);
};
class derived : public base {
public:
    // nothing to override here 'cause f() is non virtual
protected:
    // because f_int() and f_dbl are unique names, override or hide f_int() will not affect f_dbl()?
    virtual int f_int(int);  // oops, will hide base::f(int x), but developer may want this on purpose
                             // no effect on f_dbl(), which is good
}
base bobj;
derived dobj;
bobj.f(int i);    // ok
dobj.f(double d); // ok

如果派生类声明void f(int),则它覆盖虚拟函数,并且隐含virtual说明符。如果派生类声明int f(int),则将隐藏基函数。我想你对此很熟悉。

当您希望其他人基于您的基类开发代码时,问题就来了。对于天真的方法,每个派生类都必须小心添加正确的覆盖,以免意外地隐藏函数,得到一个正在工作但错误的程序(即用户说f(),但得到了错误的东西)。使用"公共非虚拟"习惯用法,用户总是秘密地调用f(),库开发人员可以通过覆盖一个唯一命名的受保护函数,只覆盖她感兴趣的部分,而不必触摸可能影响其他用户的名称。

我所能看到的是,它会降低链接到SomeDerived::f()而不是使用虚拟Base::f()的可能性,尽管到目前为止我还不记得有编译器犯过这种罪。正在寻找其他人提出更好的答案。。。

至少从历史上看,使用非公共虚拟功能一直是通过合同支持编程。这个Base中的(非虚拟)公共函数定义了一个契约前条件和后条件的assert(或类似的东西)以及不变量。它们转发到私有或受保护的虚拟功能实际工作。因此,例如,经典的clone函数可以定义为:

class Base
{
    virtual Base* doClone() const = 0;
public:
    Base* clone() const
    {
        Base* results = doClone();
        assert( typeid(*this) == typeid( *results ) );
        return results;
    }
};

clone的情况下,这种保护可能是过度的;我还没有看到任何由于派生类实现错误而导致的问题(如果存在多个级别的推导)。然而,对于大多数其他功能开发健壮软件的有效手段。

如果您在接口中有重载函数,则不同的实现,我看不出有任何真正的理由不过载虚拟功能。

最新更新