考察以下代码:
class AbstractBase { public: virtual IBase() = 0; virtual Interface() const = 0; virtual const char* Member() const {return _member;} protected: char *_member; }
这段代码有个问题, 虽说这个类是个抽象的, 但它仍然需要一个构造函数来初始化 _member.
可能有人觉得, 这个 AbstractBase 的设计者试图让每一个它的 derived class 提供 _member的初值. 但是如果是这样, derived class 的唯一要求就是 AbstractBase 必须提供一个带有唯一参数 protected constructor:
AbstractBase:: AbstractBase(char *member_value = 0) :_member(member_value){}
一般而言, class 的data member 应该被初始化, 并且只是在 constructor 中或是在 class member functions 中指定初值. 其他任何操作都将破坏封装性质, 使 class 的维护和修改更加困难.
当然你也可能说设计者的错误不在于未提供一个explicit constructor 而是他不应该在一个 抽象的 base class 中声明 data members. 接口的思想固然没错, 但是把共享的数据提出放在 base class 里也是名正言顺的.
C++ 新手(比如我) 常常很惊讶的发现, 一个人竟然可以定义和调用一个 pure virtual function; 不过它们只能被静态调用, 不能经由虚拟机制调用. 例如以下代码是合法的:
inline void AbstractBase::Interface() const { //定义之前的纯虚函数 Interface() const } inline void ConcereteDerived::interface() const { //调用一个纯虚函数 AbstractBase::Interface(); }
具体要不要这么做, 取决于 class 的设计者,. 唯一例外的就是 pure virtual destructor; class 的设计者一定要定义它. 因为每一个 derived class destructor 都会被编译器扩展, 以静态调用的方式调用其每一个 virtual base ckass 以及上一层 base class 的 destructor. 因此只要少了一个就会导致链接失败.
PS. 我用的 VC++ 12.0 编译器显示的是:
error LNK2019: 无法解析的外部符号 "public: virtual void __thiscall
和 fatal error LNK1120: 1 个无法解析的外部命令;
在 gcc 4.8.1 上显示错误是:
undefined reference to `IBase::Interface() const‘
这个...我很期待使用 Clang 或 icc 的朋友可以编译这段代码看下能不能成功...
但是段代码 Lippman 说是合法的, 我姑且就信了吧.
有人可能觉得, 对一个 pure virtual destructor 的调用操作不是应该在编译器扩展 derived class的额 destructor 的时候压抑下来吗? 不, class 设计者可能已经真的定义了一个 pure virtual destructor, 这样的设计是以 C++ 语言的一个保证为前提: 集成体系中的每一个 class object 的 destructor 都会被调用, 所以编译器不能压抑这个操作.
你也可能觉得, 编译器会不会合成一个 pure virtual destructor 的函数定义呢? 可惜编译器不行啊, 因为它对一个执行文件采取的是分离编译模型, 开发环境可以提供一个设备, 在链接时找出 pure virtual destructor 不存在的事实体, 然后重新激活编译器, 赋予一个特殊指令, 当然目前是否有编译器真这么做还不知道.
虚拟规格的存在
如果咱们真的把 AbstractBase::Member() 设计为一个 virtual function, 那将是一个糟糕的选择, 因为其函数定义并不与类型有关, 所以不会被后继的 derived class 改写. 此外, 又因为它的 non-virtual 函数实体是一个 inline 函数, 如果常常被调用的话, 效率上的报应可是不轻.
那么编译器能不能分析出该函数实际上只有一个实体存在于 class 层次体系中? 如果可以, 能不能把调用操作转换成一个静态调用操作, 以允许该调用操作的 inline expansion? 如果 class 层次体系陆续被加入新的 classes, 带有这个函数的新实体, 又当如何? 没错, 新的 class 会破坏优化! 该函数会被重新编译(或是产生第二个实体, 编译器决定那个被调用). 但是, 欲挖出这样的相依性, 有可能需要某种形式的 persistent program database 或 libray manager.
所以说, 把所有的成员函数都声明为virtual function, 然后再靠编译器的优化操作把非必要的 virtual invocation 去除, 并不是个好主意.
虚拟规格中 const 的存在
决定一个 virtual function 是否需要 const 似乎是一个琐碎的事, 但当你真的认真考虑的时候, 却不容易做决定. 做这件事情, 意味着要假设 subclass 实体可能会被无穷次的使用, 不把函数声明为 const, 又意味着该函数不能够获得一个 const reference 或 const pointer.更恶心的是, 声明一个函数为 const 之后才发现实际上其 derived instance 必须修改一个 data member...
所以给的建议就是, 不要给 virtual function 加个 const.
结论:
靠谱的类是避免头疼的保证:
class AbstractBase { public: virtual ~AbstractBase() = default; virtual void Interface() = 0; const char* Member() const {return _member;} //就一个实体你跟人家学什么虚拟? protected: Abstractbase(char *member = 0):_member(){} //一个带参数的 constructor char *_member; };