在NVI成语下,为什么虚拟功能不能公开?

Under the NVI idiom, why can't the virtual function be public?

本文关键字:能不能 功能 虚拟 为什么 NVI 成语      更新时间:2023-10-16

c++私有和受保护的虚方法和不使用公共虚方法是否有任何正当理由?讨论了非虚接口(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在类定义的公共部分有什么影响?

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习惯用法并不是一个语法要求,而是一个在所有派生类上强制基类语义(日志记录或缓存)的工具。

不,语言中没有任何东西可以阻止您创建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(...) ); 

中析构函数实际上不会释放内存(是的,我以前不得不处理这种类型的场景)。语言并没有明令禁止,但这通常是个坏主意。换句话说,仅仅因为它是合法的,并不意味着它是道德的。这就是习语的概念。