NVI
non-virtual interfaces(NVI),非虚拟接口,私有虚函数。
令客户通过 public non-virtual 成员函数间接调用 private virtual 函数,是 Template mothod 设计模式的一种独特表现形式。这个 non-virtual 虚函数称为 virtual 函数的外覆器。(摘自《Effective C++》)
简单示例:
#include <iostream>
#include <vector>
#include <memory>
#include <assert.h>
class System {
private:
std::vector<std::string> personNames;
public:
void add(const std::string &name) {
personNames.emplace_back(name);
}
} ;
// 基类, 访问
// public non-virtual 接口 --> add 外覆器
// private virtual 接口 --> 强制派生类实现(缺省也可)
class Access {
public:
Access(System *const _ptr) : ptr(_ptr) {}
virtual ~Access() = default;
void add(std::string name) {
// 可以加入一些逻辑判断, 验证是否合法
if(ptr == nullptr) return;
// 可以设置适当场景, 例如加入线程锁控制等
addPerson(name); // 调用派生类的 virtual 函数, 真正的数据操作
// 调用结束之后清理场景, 打日志等
logCall(name);
}
private:
virtual void addPerson(std::string name) = 0 ;
static void logCall(const std::string &name) {
std::cout << "name " << name << " is added to System" << std::endl ;
}
private:
System *ptr;
} ;
// 派生类, 学生访问
class studentAccess : public Access {
public:
studentAccess(System *const _ptr)
: Access(_ptr),
ptr(_ptr) {}
private:
// 直接进行纯数据操作
virtual void addPerson(std::string name) override {
ptr->add(name);
}
private:
System *ptr;
} ;
int main () {
std::shared_ptr<System> nameHolder = std::make_shared<System>();
std::shared_ptr<Access> stuVisit = std::make_shared<studentAccess>(nameHolder.get());
stuVisit->add("YHL");
stuVisit->add("Fluence");
return 0;
}
基类的 public non-virtual 外覆器可以确保在真正的 virtual 函数操作之前设定好适当场景,并在调用结束后清理场景。“事前工作”可以包括锁定互斥器(lock a mutex),制造运转日志记录项(log entry),验证 class 约束条件,验证函数先决条件等;“事后工作”可以包括解除互斥器(unlock a mutex),制造运转日志等。(摘自《Effective C++》)
特点:
(1)接口与实现分离,基类负责逻辑和事前事后工作,派生类专心负责数据操作。
(2)基类更加稳定。倘若派生类实现部分改动,只需要重新编译派生类所在文件即可,不影响基类的逻辑部分。基类掌控接口所有权——如果不是 NVI,采用的是普通的虚函数覆盖机制,基类中加入的逻辑判断一旦改动,在所有派生类中的逻辑部分都要改动(因为派生类的 virtual 虚函数会覆盖基类的 virtual 虚函数),这些文件都要重新编译。一定程度上,NVI 的基类集中了对逻辑的掌控,而且不能被子类覆盖,这就是“ public non-virtual ” 接口,不是虚函数的原因。而 virtual 接口是 private 的原因——防止越过“逻辑部分”直接调用 virtual 函数的情况,纯数据操作如果不加入一些逻辑或者线程保护容易出现 bug。