C++ 中提供的四种新式转型
1.const_cast<T> (expression)
2.dynamic_cast<T> (expression)
3.reinterpret_cast<T> (expression)
4.static_cast<T> (expression)
旧式转换仍然合法,但是新式转换较受欢迎, 其原因如下:
如果你认为转型操作其实什么也没做,只是告诉编译器把某种类型视为另外一种类型.这其实是错误的.比如int和double其实在底层表示是不一样没办法把一个double,用int的方式解释一下就变成int类型了.底层还是做了一定的转换的.带虚函数的父子类之间的转换,也不是仅仅从编译器层面,把一种类型解释为另外一种类型而已.
异常安全的两个条件:
下面是一个非异常安全实现的函数:
class PrettyMenu {
public:
...
void changeBackground(std::istream& imgSrc); //改变背景图像
...
private:
Mutex mutex; //互斥器
Image *bzImage; //目前的背景图像
int imageChanges; //背景图像被改变的次数
};
void Pretty::changeBackground(std::istream& imgSrc)
{
lock(&mutex);
delete bgImage;
++imageChanges;
bzImage = new Image(imgSrc);
unlock(&mutex);
}
上面的代码是不具备异常安全的,首先如果new Image发生了异常,那么会导致永远无法unlock锁资源了.导致了资源泄露并且new Image的异常也会导致数据被破坏,因为原来的bgImage已经被delete了,并且imageChanes被递增了.因此又导致了数据败坏,两个异常安全的条件都不满足.资源泄露的问题很好解决,可以利用对象来管理资源,在离开作用域后利用对象的析构函数来释放资源,代码如下:
Lock ml(&mutex);
delete bgImage;
++imageChanges;
bzImage = new Image(imgSrc);
现在只剩下做好一个问题了,数据败坏的问题.条款11中提供了一种方法来避免数据败坏的问题.在删除数据的时候,应该先保存原来的数据,然后再new一个新的数据,只有在new成功的时候,才会delete原来的数据.此外还可以使用copy-and-swap来避免数据败坏的问题.
异常安全函数的三个基本保证:
一个异常安全的函数,必须满足上面三个基本保证中的一个.一个强烈保证的实现:
//将bzImage按照shared_ptr来管理,只有在new Image成功的情况下,才会delete内内部的指针,因此下面这段代码达到了强烈保证的需求. Lock ml(&mutex);
bzImage.reset(new Image(imgSrc));
++imageChanges;
除了利用上面的方法来达到强烈保证外,还有一个一般化的设计策略可以达到强烈保证,那就是copy-and-swap,原则很简单,为你打算修改的对象做出一份副本,然后在那副本上做一切必要的修改,若有任何修改动作抛出了异常,原对象保持未改变的状态.待所有的改变完成后,再与原对象在一个不抛出异常的操作中置换.基于copy-and-swap达到强烈保证,代码如下:
//这里把imageChanges和bgImage通过pimpl idiom手法管理起来
using std::swap
Lock ml(&mutex);
shared_ptr<PMImpl> pNew<new PMImpl(*pImpl);
pNew->bgImage.reset(new Image(ImageSrc)); //修改副本
++pNew->imageChanges;
swap(pImpl,pNew); //置换数据,释放mutex
强烈保证
往往能够以copy-and-swap
实现出来,但是”强烈保证”并非对所有函数都可能.异常安全保证
通常最高只等于其所调用的各个函数的异常安全保证
中的最弱者.<classname>fwd
),例如标准库中的iosfwd 完全且仅有声明式
的形式存在,这种做法不论是否涉及到templates都适用.在C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味着”is-a”的关系.public继承”意味着is-a”适用于base classes身上的每一件事情也一定适用于derived classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base class对象.
名称查找规则:
1. local作用域也就是使用变量的成员函数所在的栈幁区域.
2. 该成员函数所在类的作用区域
3. 基类作用域内
4. 基类所在namespace内
5. global namespace内
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int) {cout << "Base::mf1(int)" << endl;}
virtual void mf2() {cout << "Base::mf2()" << endl;}
void mf3() {cout << "Base::mf3()" << endl;}
void mf3(double) {cout << "Base::mf3(double)" << endl;}
};
class Derived:public Base {
public:
virtual void mf1() {cout << "Derived::mf1()" << endl;}
void mf3() {cout << "Derived::mf3()" << endl;}
void mf4() { cout << "Derived::mf4()" << endl;}
};
int main()
{
Derived d;
int x;
d.mf1(); //运行没问题,调用的是Derived::mf1()
//d.mf1(x); //运行错误,Derived遮掩了Base的mf1的名称,因此调用的是Derived::mf1,但是找不到对应的函数
d.mf2(); //运行没问题 调用的是Base::mf2,名称没有被遮掩
d.mf3(); //运行没问题 调用的是Derived::mf3()
//d.mf3(x); //运行错误,Derived遮掩了Base的mf3的名称,因此调用的是Derived::mf3,但是找不到对应的函数
}
通过在Derived类中添加下面的using声明可以解决这个问题
class Derived:public Base {
public:
//让Derived在其作用域内看到Base类中的mf1和mf3函数,避免了遮掩问题.
using Base::mf1;
using Base::mf3;
....
上面的using Base::mf1,会将Base类中所有的名为mf1的重载函数都让其在Derived作用域内可见,如果你只想让mf1中的某一个重载函数在Derived中可见,就可以使用转交函数,下面是转交函数的使用方法:
比如:对于Base中只想让Base::mf1(int)可见,那么可以在Derived中写上一个mf1(int)的函数,然后在内部显示调用Base::mf1(int)
class Derived:public Base {
........
virtual void mf1(int x)
{
Base::mf1(x);
}
........
};
NVI(non-virtual interface)手法,它是所谓的Template Method设计模式的一个独特表现形式.这个手法就是令客户通过public non-virtual成员函数间接调用private virtual函数.这个手法的优点在于,可以在正式调用private virtual函数之前做一些准备工作和善后工作.virtual函数也可以不比是私有的,甚至在有些场景下,virtual函数应该是public的,否则会导致不能实施NVI手法.
stragtegy设计模式的目的就是将机能从成员函数移到class外部.但是这带来的缺点就是非成员函数无法访问class的non-public成员
public继承意味着is-a的关系,适用于基类的每一件事,也适用于派生类.因为每一个派生类对象都是一个基类对象,派生类一定会继承基类的non-virtual函数,如果派生类重新定义了继承过来的non-virtual函数,那么就会出现矛盾,派生类不再是一个基类对象了.因此建议绝不要重新定义继承而来的non-virtual函数.
绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数,你唯一需要覆写的东西确实动态绑定的,如果派生类将继承而来的缺省参数修改了,那么会导致调用的是派生类的virtual函数,但是缺省参数用的是基类的缺省参数,因为静态类型是基类,动态类型是派生类.
has-a和is-a具有完全不同的意义,has-a实际有两个意义,有一个或根据某物实现出.is-a和has-a的关系还是很好区别的,比如一个人,一个地址,还有电话号码,你不能说一个人是一个地址,这里只能是has-a的关系一个人含有地址,电话号码等信息.当has-a发生在应用域内,是有一个的意义,像车子,人,这些都是应用域的部分,像互斥器,缓冲区查找树等发生在实现域内,表现的是根据某物实现的含义.