为何C++标准库会采用仅调用私有纯虚函数的公有方法模式?
在学习C++17库特性时,我发现部分公有方法仅调用私有纯虚函数,例如:
std::pmr::memory_resource::allocate
void* allocate( std::size_t bytes, std::size_t alignment = alignof(std::max_align_t) );分配至少bytes字节的存储空间,对齐至指定alignment。等价于
return do_allocate(bytes, alignment);
std::pmr::memory_resource::do_allocate
virtual void* do_allocate( std::size_t bytes, std::size_t alignment ) = 0;
以及:
std::basic_streambuf<CharT,Traits>::pubsync, std::basic_streambuf<CharT,Traits>::sync
(1)
int pubsync();
protected:
(2)virtual int sync();
- 调用最派生类的
sync()
我查阅了相关问答及NVI惯用法的文章,但未找到对这类仅调用私有方法且无重载的情况的解释。
解答
这种设计属于非虚接口(NVI)惯用法的典型应用,哪怕当前公有方法只是简单转发调用,背后也有明确的设计考量:
保障接口长期稳定:公有非虚方法是对外暴露的稳定契约。未来标准库可以在这个入口中添加通用逻辑,比如参数合法性校验、内存分配统计、异常包装、线程安全保障等,而无需修改子类的实现代码。如果直接将虚函数设为公有,后续扩展时会破坏原有接口,迫使所有子类同步修改。
统一控制调用流程:通过公有方法作为唯一入口,基类可以确保所有对该功能的调用都遵循统一的执行路径。即使当前没有额外逻辑,也为未来的全局扩展预留了空间,避免子类绕过基类的控制逻辑。同时,私有纯虚函数只能被基类调用,子类仅能实现具体逻辑,无法篡改调用入口的行为。
规避默认参数的绑定陷阱:C++中虚函数的默认参数是编译期绑定的,如果将带默认参数的虚函数设为公有,子类覆盖时可能出现默认参数不一致的问题(比如基类默认参数是A,子类实现时用了B,但调用时可能还是用基类的默认值)。通过公有非虚方法携带默认参数,再将参数传递给无默认值的私有虚函数,就能彻底避免这个问题。
保持设计风格一致性:C++标准库整体遵循NVI的设计惯例,这种模式能让熟悉标准库的开发者快速理解接口结构——公有方法是对外的使用入口,私有/保护虚函数是留给子类的扩展点,降低学习和使用的成本。
内容的提问来源于stack exchange,提问作者sridoo

