Exceptional_C++
Table of Contents
1 编译防火墙和Pimp惯用法
- 对于函数的参数类型和返回值类型来说,只需要前置申明就足够了 被继承的基类必须有完整的定义,这样编译器才能确定子类对象的大小,虚函数以及其他基本信息. 定义类中的成员变量类型,必须有完整的定义(指针不需要),这样才能确定子类对象的大小
- 如果只要流的前置申明,应该优先使用#include<iosfwd>
- 如果只需要前置申明,绝对不要用#include包含相应的头文件
- 对于广泛应用的类,应该优先使用Pimpl惯用法来隐藏实现细节,通过一个不透明的指针(指向一个进行了前置声明但又没有定义的类)来保存私有成员(包括成员变量和成员函数).
- 不能将虚成员函数放在Pimpl隐藏起来,这是为了能够在更深层次的派生类中进行覆盖
- Pimpl中的函数可能需要一个指向可见对象的回指指针,并通过这个指针来调用可见类中的函数,这将会增加一个间接层
- 通常,最好的折中方法就是将所有的私有成员(包括成员变量和函数)放入Pimpl中,并将只有私有成员才会调用的非私有函数也放入Pimpl中.
- protected成员永远不应该被放入Pimpl中,因为这样会使保护成员失去其应有的作用.
- 在某些情况下可以将Pimpl写成与原来的类完全一样的形式,而将原来的类写成仅由转调函数组成的共有接口.但这样原来的类在继承时基本无用了.
- 只有当性能分析和经验教训都证明了,确实需要有额外的性能提升时,才使用Pimpl惯用法,并且在通常情况下使用pimpl,而在特殊情况下使用Fast Pimpl(使用定容量的Allocator)
2 名字查找,名字控件和接口规则
- 如果在声明函数的参数时,使用了一个类(例如NS::T),那么在查找正确的函数名字时,编译器在包含参数类型的名字空间中也会进行参数名字的匹配.
- namespace NS
- {
- class T{};
- void f(T);
- }
- NS::T parm;
- int main()
- {
- f(parm);
- }
- 所有的函数(包括自由函数,非类的成员函数)也可以在逻辑上看成是类X的一部分(它们可以看作是类X接口的一部分),如果它满足:
- 使用了类X
- 与X同时被定义(在同一个头文件/同一个命名空间中)
- 如果在客户代码中定义了一个使用类X的函数,并且这个函数与X所在的命名空间(不是类空间!),并且这个函数与X所在的命名空间中的一个函数原型相匹配,那么这个函数的调用将出现二义性
- namespace A
- {
- class X{};
- void f(X);
- }
- namespace B
- {
- void f(A::X);
- void g(A::X param)
- {
- f(param) //二义性:是A::f还是B::f?
- }
- }
- 根据接口规则,成员函数与类的关系要强于非成员函数与类的关系,例如
- namespace A
- {
- class X{};
- void f(X);
- }
- class B
- {
- void f(A::X);
- void g(A::X param)
- {
- f(param) //不存在二义性,使用B::f,因为成员函数与类的关系更强于非成员函数与类的关系
- }
- }
- 从接口规则中可以推断出如果A和B是两个类,并且f(A,B)是一个自由函数,那么
- 如果A和f同时被定义,那么f就是A的一部分,因此A将依赖于B
- 如果B和f同时被定义,那么f就是B的一部分,因此B将依赖于A
- 如果A,B和f同时被定义,那么f即是A的一部分,也是B的一部分,因此A和B相互依赖
- 派生类中的名字隐藏问题在所有的嵌套作用域中也同样存在,其中包括命名空间.
- 当在基类/派生类的名字隐藏中遇到这样的问题时,可以有两种解决方案:
- 让调用代码明确第指出它想调用的函数
- 通过using申明语句使它向调用的函数在正确的作用域中是可见的.
- 正确地使用命名空间.如果将一个类放入命名空间,那么同时要保证将这个类的所有辅助函数和运算符函数也放入相同的命名空间.否则,你将在代码中发现奇怪的结果.
Date: 2013-04-14 22:51:54 中国标准时间
Author:
Org version 7.8.11 with Emacs version 24
Validate XHTML 1.0
本文出自 “暗日” 博客,请务必保留此出处http://darksun.blog.51cto.com/3874064/1178200