【问题标题】:Under the NVI idiom, why can't the virtual function be public?NVI成语下,虚函数为什么不能公开?
【发布时间】:2015-12-12 02:02:25
【问题描述】:

C++ private and protected virtual methodIs there any valid reason for not using public virtual methods? 正在讨论非虚拟接口 (NVI) 和非公共虚拟功能及其共生关系。 Scott Meyers 在 Effective C++ 中也说过

有时虚函数甚至必须是公开的,但 NVI 习语不能真正应用。

我没有看到为什么 NVI 要求实现特定的虚拟功能是非公开的?从 Herb Sutter 的文章 Virtuality 中,它说这是一个很好的做法,例如,将公共(客户端)接口与实现细节(非公共接口)分开是很好的。我想知道的是,如果将此类虚函数声明为公共,我是否错过了任何语义上阻止应用 NVI 的语言功能?

例如:

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

如果我将SetStateBoolSetStateInt 放在类定义的公共部分会有什么影响?

【问题讨论】:

  • 它可以防止客户依赖他们,仅此而已
  • 如果是公开的,就是接口的一部分,所以接口毕竟不是非虚拟的……
  • @T.C.不错哦!它违背了名称的 NV 部分......
  • 你是在问fpr这个成语的原因,还是为什么NVI暗示虚函数是非虚函数?
  • 应该是。我不明白你的困惑。如果您有公共虚拟功能,那么您将不再有非虚拟接口,但该语言中没有任何内容要求您首先遵循 NVI Isom。与所有成语一样,这只是一个建议。

标签: c++ idioms non-virtual-interface


【解决方案1】:

TLDR:可以,但不应该。

假设您要确保正确记录对公共接口的每次调用(例如金融服务法律要求)

class Engine
{
public:
    void SetState( int var, bool val );
    {  
        logToFile(); 
        SetStateBool( int var, bool val ); 
    }

    void SetState( int var, int val );
    {   
        logToFile();
        SetStateInt( int var, int val ); 
    }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;
    void logToFile();    
};

由于公共接口是非虚拟的,所有派生类也自动具有日志记录。相反,如果您将 SetStateBoolSetStateInt 设为公开,则无法对所有派生类强制执行日志记录。

因此,使用 NVI 习语的建议不是句法要求,而是在所有派生类上强制执行基类语义(日志记录或缓存)的工具类。

【讨论】:

    【解决方案2】:

    不,语言中没有任何东西可以阻止您创建实现函数public。原则上,您可以执行以下操作:

    class Base {
    public:
    
       virtual ~Base(){}
    
       void work() { do_work(); }
       virtual void do_work() = 0;
    
    };
    

    实现是公开的。 Meyers 说有时您必须这样做,他可能是在说开发人员有时会受到限制,无法在设计不佳的环境中进行开发。

    例如,您可以违背 RAII 习语并执行以下操作:

    std::unique_ptr<MyClass,DoNothingDeleter> ptr ( new MyClass(...) ); 
    

    析构函数实际上不会释放内存(是的,我之前不得不处理这种类型的场景)。语言并没有禁止它,但它通常是一个坏主意。换句话说,仅仅因为它是合法的,并不意味着它是道德的(信用马歇尔克莱恩)......这就是成语的概念。

    【讨论】:

    • std::unique_ptr&lt;MyClass,DoNothingDeleter&gt; ptr ( new MyClass(...) ); 显然是恶意代码或故意混淆代码。你介意扩展你的经验吗?
    • 它既不是恶意的,也不是故意混淆的……而是一种处理std::ostream对象的方法,该对象可以由用户指定为std::cout(你不敢尝试拥有它)或std::fstream,这是完全可以拥有的。通过拥有两个派生类,我最终摆脱了DoNothingDeleter 的丑陋——一个拥有,一个不拥有,接口类不关心所有权语义。
    猜你喜欢
    • 2013-06-28
    • 2011-03-04
    • 2010-09-07
    • 2011-11-20
    • 2012-12-16
    • 1970-01-01
    • 2020-09-17
    • 2011-10-25
    • 2012-09-15
    相关资源
    最近更新 更多