4 技巧
~~~~~~~
4.1 如何将构造函数和非成员函数虚拟化
=====================================
1. 虚拟构造函数是指能够根据输入给它的数据的不同而建立不同类型的对象的成员函数(不是构造函数!!).
2. 虚拟拷贝构造函数返回一个指针,指向调用该函数的对象的新拷贝.因此其名字通常为clone()
3. 被派生类重定义的虚拟函数不用必须与基类的虚拟函数具有一样的返回类型:
如果函数的返回类型是一个指向基类的指针(或一个引用),那么派生类的函数可以返回一个指向基类的派生类的指针(或引用)
4. 根据参数的不同动态类型而行为特征也不同的非成员函数叫做虚拟化的非成员函数
5. 虚拟化的非成员函数实际上也是通过参数中的成员函数来实现的.
4.2 如何限制某个类能产生的对象数量
===================================
1. 在类中的静态对象总是被构造和释放的,及时不使用该对象.
函数中的静态对象只有在第一次执行函数时才会建立对象.
2. 我们能够准确地知道函数的静态成员在第一次执行定义静态成员的函数时初始化.
而无法定义一个类的静态成员被初始化的时间.
3. 带有内部链接的函数可能在程序内被复制,这种复制也包含函数内的静态对象.
因此如果建立一个包含局部静态对象的非成员内联函数,你可能使程序的静态对象的拷贝超过一个.
所以,不要建立包含局部静态对象的非成员函数的内联函数.
4. 可以在public:中使用using语句using private函数,这样可以把private函数恢复public访问权限.
4.3 如何要求/禁止在堆中产生对象
================================
4.3.1 要求在堆中建立对象
-------------------------
1. 非堆函数在定义它的地方被自动构造,在生存时间结束时自动释放,所以要禁止使用隐式的构造函数和析构函数就可以实现这种只能通过new手段建立对象的限制了.
最好让析构函数成为private(结果是该类不能被继承)或protected(该类可以被继承),让构造函数成为public.
你可以引进一个专用的伪析构函数,用来访问真正的析构函数.客户端调用伪析构函数释放他们建立的对象.(注意,异常处理体系要求所有在栈中的对象的析构函数必须申明为公有!)
- class A
- {
- public:
- void destory()const {delete this;} //伪析构函数
- private:
- ~A(){}
- };
- int main()
- {
- //A a; //编译出错 error: 'A::~A()' is private
- A* a = new A(); //能编译通过
- //delete a //错误,~A() is private
- a->destory; //正确,通过调用伪析构函数,访问真正的析构函数.
- }
4.3.2 判断对象是否在堆中.
--------------------------
1. 在new一个数组时,只会调用一个operator new[],执行多次构造函数.因此不能通过在operator new和operator new[]中设置成员变量的方式来判断对象是否在堆中.
2. 静态对象可能分配在堆中也可能分配在栈中,它们的位置依据系统而定.
3. 我们可以通过抽象混合(mixin)基类给派生类提供判断指针指向的内存是否由operator new分配的能力.
4.3.3 禁止堆对象
-----------------
1. 禁止对象在堆中直接实例化
由于new操作符总是调用operator new函数,因此可以自己声明operator new函数,并把它作为private权限.
2. 禁止对象作为派生类的基类在堆中实例化
如果operator new和operator delete没有在派生类中进行声明,它们就会继承基类中的版本,因此,基类中声明了private的operator new和operator delete,也能阻碍类作为一个位于堆中的派生类对象的基类被初始化.
如果派生类声明它自己的operator new,当在堆中分配派生对象时,就会调用自己的operator new函数.这就得另找一个不同的方法来防止基类的分配问题.
3. 对象被嵌入到其他对象内,在堆中实例化
类的operator new是private这一点,不会对包含该类对象的对象分配产生任何影响.
4.4 智能指针
=============
1. 因为当调用auto_ptr的拷贝构造函数时,对象的所有权被传递出去,所以通过传值方式传递auto_ptr对象是一个很糟糕的方法.
2. T& operator*()的返回值需要设置为reference
3. 一个智能指针如果提供了默认转换为原始指针,则可能出现delete smart_ptr<T>的写法,这样会导致一个对象指针被delete两次.
因此除非有必要,否则尽量不要提供转换到原始指针的隐式类型转换符.
4.4.1 智能指针和基础类到基类的类型转换
---------------------------------------
1. smart_ptr<Base>和smart_ptr<Sub>之间并无任何关系,不能认为这两个智能指针之间存在继承关系.
1) 但是可以通过在smart_ptr<Sub>中定义隐式类型转换符operator smart_ptr<Base>的方式,模拟子类与基类指针之间的转换.
这个方法有两个缺点:一,你必须认为特化smart_ptr<Sub>,这就破坏了模板的通用性.二,你可能必须添加许多类型转换符,因为你的对象可能位于继承层次中很深得位置.
2) 声明(非虚)成员函数模板(成员模板),使用它来生成灵巧指针类型转换函数.它的原理与第一种方法一样的
- template<class newType>
- operator Smart_ptr<newType>()
- {
- return Smart_ptr<newType>(m_pointee);
- }
这种方法也有个缺点,如果定义了函数Func(Smart_Ptr<Base>)和Func(Smart_Ptr<Sub>),那么对于Sub的子类SubSub,调用Func(Smart_Ptr<SubSub>)具有二义性,不知道把它转换为Smart_Ptr<Base>还是Smart_Ptr<Sub>
3) 综上所述,无法让智能指针的行为与原始指针一样.最好的方法是使用成员模板生成类型转换函数,在会产生二义性结果的地方使用casts
4.4.2 智能指针和const
----------------------
1. 由于智能指针实质上只是一个类,所以const smart_ptr<C>表示智能指针中的pointee是不能变的.
要表示智能指针指向一个const对象,需要定义为smart_ptr<const C>.
2. 由于smart_ptr<C>和smart_ptr<const C>是两个完全不同的类型,因此不能用smart_ptr<C>初始化smart_ptr<const C>
3. 由于const和non-const指针有类似于父类与子类的关系,因此可以考虑通过继承的方式实现smart_ptr和const_smart_ptr
- const_smart_ptr<T>
- {
- const T* cpt;
- }
- smart_ptr<T>::public const_smart_ptr<T>
- {
- T* pt
- }
4.5 引用计数
=============
4.6 代理类
===========
1. const函数的调用与否,与其返回值是作为左值还是右值无关,而仅仅与调用成员函数的对象的const属性有关.
2. operator[]无法判断其返回值会被用于左值还是右值,这是可以实现一个代理类,在该代理类中包含原类的一个引用,当对代理类写时就可以对原对象做个拷贝.
4.7 让函数根据一个以上的对象来决定怎么虚拟
===========================================
1. 无名命名空间的东西是当前编译单元(其实就是当前文件)私有,这很像文件范围内的static函数一样,有了命名空间之后,文件范围的static已经不赞成使用了.
2.