随笔- 423  文章- 0  评论- 22 

【C++程序设计技巧】NVI(Non-Virtual Interface )

作者:gnuhpc  
出处:http://www.cnblogs.com/gnuhpc/

在C++的程序设计中有一些设计开发的典型技巧需要整理讨论,在此抛砖引玉,为自己做积累,请高人也多多指教。

1.简介

在标准C++库中我们可以看到这样的一个现象:

6个公有虚函数,并且都是std::exception::what()和其重载。

142个非公有虚函数。

这样设计的目的何在呢,为什么“多此一举”的把虚函数设置为非公有呢?

这就是NVI机制要求的:将虚函数声明为非公有,而将公有函数都声明为非虚——虚拟和公有选其一。

2.机制分析

程序员常常将基类中的虚函数公有化,来提供一个接口的定义(virtual的功劳)同时提供其实现(具体的一个实现)。

view plainprint?
  1. class Base{
  2. public:
  3. virtual void Foo(int){
  4. cout<< "Base's Foo!" << endl;
  5. };
  6. };

问题就出在“同时”——一个定义了接口的形式,一个定义了默认的一个实现,显然这样的设计没有将接口定义和实现分来。在这个时候,我们可以使用模板方法模式的思想:

view plainprint?
  1. class Base{
  2. public:
  3. void Foo(){
  4. DoFoo1();
  5. DoFoo2();
  6. }//use DoFooX()
  7. private:
  8. virtual void DoFoo1(){
  9. cout << "Base's DoFoo1" <<endl;
  10. }
  11. virtual void DoFoo2(){
  12. cout << "Base's DoFoo2" <<endl;
  13. }
  14. };
  15. class Derived: public Base{
  16. private:
  17. virtual void DoFoo1(){
  18. cout << "Derived's DoFoo1" << endl;
  19. };
  20. };

函数Foo定义了接口的形式,而DoFooX()函数则实现了对Foo函数的行为定制,实现了接口定义和实现的分离,我们举一个例子来说明好处:如果我们希望在Foo中做一下CS(Critical Section)的加锁解锁控制:

若我们完成这样的接口与实现分离,那么我们的实现是在基类的接口处添加所需流程即可,子类不需要修改:

view plainprint?
  1. class Base{
  2. public:
  3. void Foo(){
  4. cout << "Locking" << endl;
  5. DoFoo1();
  6. DoFoo2();
  7. cout << "Unlocking" << endl;
  8. }//use DoFooX()
  9. private:
  10. virtual void DoFoo1(){
  11. cout << "Base's DoFoo1" <<endl;
  12. }
  13. virtual void DoFoo2(){
  14. cout << "Base's DoFoo2" <<endl;
  15. }
  16. };
  17. class Derived: public Base{
  18. private:
  19. virtual void DoFoo1(){
  20. cout << "Derived's DoFoo1" << endl;
  21. };
  22. };

若不实现接口与实现分离,则从基类到子类都需要修改:

view plainprint?
  1. class Base{
  2. public:
  3. virtual void Foo(){
  4. cout << "Locking" << endl;
  5. cout << "Base's Foo" << endl;
  6. cout << "Unlocking" << endl;
  7. }
  8. };
  9. class Derived: public Base{
  10. public:
  11. virtual void Foo(){
  12. cout << "Locking" << endl;
  13. cout << "Derived's Foo" << endl;
  14. cout << "Unlocking" << endl;
  15. };
  16. };

注意,当且仅当子类需要调用基类的虚函数时才将虚函数设置为protected(否则没有权限),并且NVI机制不适用于析构函数,对于析构函数,如果设为公有则应该设置为虚拟(在允许多态删除的基类中),否则设置为私有或者protected的非虚拟形式(不含多态删除的基类中)。

带来的风险:

首先是FBC问题(Fragile Base Class ),下边是一个例子:

view plainprint?
  1. class Set {
  2. std::set<int> s_;
  3. public:
  4. void add (int i) {
  5. s_.insert (i);
  6. add_impl (i); // Note virtual call.
  7. }
  8. void addAll (int * begin, int * end) {
  9. s_.insert (begin, end);   //  --------- (1)
  10. addAll_impl (begin, end); // Note virtual call.
  11. }
  12. private:
  13. virtual void add_impl (int i) = 0;
  14. virtual void addAll_impl (int * begin, int * end) = 0;
  15. };
  16. class CountingSet : public Set {
  17. private:
  18. int count_;
  19. virtual void add_impl (int i) {
  20. count_++;
  21. }
  22. virtual void addAll_impl (int * begin, int * end) {
  23. count_ += std::distance(begin,end);
  24. }
  25. };

如果此时我们在父类中修改了addAll函数,改为将从begin到end的数字都调用一遍add函数,那么,子类的功能就紊乱了——子类计数就会多记录一倍(因为在子类中,add_impl每次都会计数一个,并且addAll_impl也会整体计数一次)。所以,为了防止出现FBC,一般一个公有非虚函数调用一个私有虚函数。

其次是性能上的考虑,毕竟多了一层函数调用。对此,参考文献2指出:“a word about efficiency: No, none is lost in practice because if the public function is a one-line passthrough declared inline, all compilers I know of will optimize it away entirely, leaving no overhead. (Indeed, some compilers will always make such a function inline and eliminate it, whether you personally really wanted it to or not, but that’s another story.)”

3.总结

将NVI机制放在脑子中吧,如果你还是不明白,一个故事化的讲述或许更加合适你。

1. 《Effective C++》Item 35 Consider Alternatives To Virtual Functions

2.http://www.gotw.ca/publications/mill18.htm

3.http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.3

4.http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.4

5.http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface

NVI(Non-Virtual Interface )相关推荐

  1. 华为-vlan间路由之SVI(switch virtual interface)

    SVI(switch virtual interface) (使用多层交换机 配置 vlanif接口) 交换机: vlan batch 10 20 interface GigabitEthernet0 ...

  2. System Verilog学习笔记—虚接口(virtual interface)

    1.虚接口(virtual interface) 1.1为什么引入虚接口? 我们知道,通过引入interface可以简化模块儿之间的连接,即interface是连接硬件的,其是硬件语言:但对于验证来说 ...

  3. PVE(Proxmox Virtual Environment)OpenWRT安装使用教程

    PVE安装教程 PVE(Proxmox Virtual Environment)安装配置 Proxmox Virtual Environment简介 Proxmox Virtual Environme ...

  4. 使用LVS(Linux Virtual Server)在Linux上搭建负载均衡的集群服务

    使用LVS(Linux Virtual Server)在Linux上搭建负载均衡的集群服务 一.基于于NAT的LVS的安装与配置. 1. 硬件需求和网络拓扑                       ...

  5. 【转】JNI(Java Native Interface)的简介

    因为刚刚接触android和java平台,对JNI到底是什么,还不是很了解,所以从CSDN转载了一篇文章,以便自己对JNI有一个认识,也跟大家分享一下. JNI是Java Native Interfa ...

  6. Linux服务器集群系统(一)—— LVS(Linux Virtual Server)简介

    背景: 九十年代中期,万维网(World Wide Web)的出现 Internet用户剧烈增长和Internet流量爆炸式地增长 Internet的飞速发展给网络带宽和服务器带来巨大的挑战 从网络技 ...

  7. Android JNI(Java Native Interface)技术介绍

    Android平台上的JNI技术介绍 JUL 15TH, 2013 | COMMENTS NDK简介 Android是由Google领导开发的操作系统,Android依靠其开放性,迅速普及,成为目前最 ...

  8. 虚函数(Virtual Function)与 纯虚函数(Pure Virtual Function)

    1>虚函数(Virtual Function) 1.1>Base Class #ifndef Animal_h #define Animal_h #include <string&g ...

  9. AXI(Advanced eXtensible Interface)协议规范

    AXI(Advanced eXtensible Interface)协议规范 参考:ARM (IHI 0022H.c) AMBA AXI and ACE Protocol Specification ...

  10. 利用LVS(Linux Virtual Server)系统实现Web服务器集群的负载均衡

    利用LVS(Linux Virtual Server)系统实现Web服务器集群的负载均衡 LVS系统结构与特点: 1. Linux Virtual Server:简称LVS.是基于Linux服务器集群 ...

最新文章

  1. 你了解的技术宅是这样吗?
  2. 边缘计算的三种模式:MEC、微云和雾计算
  3. 设计案例——点和圆的关系
  4. mipi的dsi全称_MIPI扫盲——DSI介绍(二)
  5. JAVA-初步认识-第六章-类与对象的关系(细节)
  6. 客户端中转request请求乱码
  7. 路由器DHCP配置及中继配置
  8. polyfit线性拟合函数
  9. Win11录屏方法介绍
  10. qq出示测试软件语音聊天,QQ语音时语音测试时可以听到声音,播放测试语音时听不到,也听不到好友发来的语音,请问怎么处理...
  11. lwIP配置宏整理(部分)
  12. 打叉图标html,SVG 勾号和叉号图标
  13. kaggle 泰坦尼克号数据分析 笔记
  14. 好书收藏:读书知多少
  15. 杨幂穿搭有三宝:露腿,收腰,配饰亮点,赶快马起来
  16. 小心肝队-冲刺日志(第三天)
  17. python求两数最大公因数_使用辗转相除法求两个数的最大公因数(python实现)
  18. html字体铺盖颜色,这5种颜色的褥子不要去买,盖上会让你的家财散尽!
  19. 为何webpack风靡全球?三大主流模块打包工具对比
  20. linux基础篇,数据流重定向

热门文章

  1. 当大数据遇到保险:传统精算模型将被颠覆
  2. 保险产业拥抱“大数据时代” 或带来颠覆性变革
  3. 学习浙江大学Photoshop设计精讲精练过程中的重难点及内容收获
  4. 基于jQuery发展历程时间轴特效代码
  5. 彻底解决Chrome自动更新方案
  6. 堆漏洞挖掘中的malloc_consolidate与FASTBIN_CONSOLIDATION_THRESHOLD
  7. java 集合元素自定义排序——Comparator.comparing , 不用实现 Comparable 接口
  8. 我的世界1.19.2最终优化模组推荐:这60个优化模组让你的体验更好
  9. C# 关于递归算法 具象化
  10. 第二章(第四部分) 黑暗之王的分身