- 索引
- 10、使用析构函数防止资源泄漏
- 11、在构造函数中防止资源泄漏
- 12、禁止异常信息传递到析构函数外
- 13、理解抛出一个异常与传递一个参数或调用一个虚函数间的差异
- 13.1 要求被作为异常抛出的对象必须被复制
- 13.2 catch中参数传递方式
- 13.3 异常匹配时可以进行类型转换
- 14、通过引用捕获异常
- 15、审慎使用异常规格
- 16、了解异常处理的系统开销
- 17、牢记80 - 20准则
C++ | 【01 基础提议】More Effective C++🧡💛💚💙
C++ | 【02 运算符】More Effective C++🧡💛💚💙
C++ | 【03 异常】More Effective C++🧡💛💚💙
C++ | 【04 效率】More Effective C++🧡💛💚💙
C++ | 【05 技巧】More Effective C++🧡💛💚💙
C++ | 【06 杂项】More Effective C++🧡💛💚💙
- 如果一个函数通过设置一个状态变量或返回错误代码来表示一个异常状态,若没办法保证函数调用者将一定检测变量或测试错误
代码,若程序继续运行,此时需要捕获异常,否则程序将会终止;
- 异常处理要能通知不可被忽略的异常状态,并需要在搜索栈空间,以便找到异常处理代码时,能确保局部对象的析构函数必须被
调用;
10、使用析构函数防止资源泄漏
当函数内部异常new的对象,没有被delete,出现内存泄漏
一般能构建一个类似auto_ptr的类对new对象进行管理;
template<class T>
class auto_ptr {
public:
auto_ptr(T *p=0) : ptr(p) {}
~auto_ptr() { delete ptr; }
private:
T *ptr;
};
11、在构造函数中防止资源泄漏
C++中删除空指针是安全的;
如何确保构造函数new对象,防止出现异常而导致资源泄漏
以下使用auto_ptr对new对象进行管理:
- 对象类型为const,故只能在构造初始化列表中初始化;
- 为了防止在初始化中new出现异常而导致资源泄漏,使用autoptr能够自动析构;
template<class T>
class auto_ptr {
public:
auto_ptr(T *p=0) : ptr(p) {}
~auto_ptr() { delete ptr; }
private:
T *ptr;
};
class Image {
public:
Image(const string& imageFilename);
};
class AudioClip {
public:
AudioClip(const string& audioFilename);
};
class BookEntry {
public:
BookEntry(const string& name,
const string& imageFilename,
const string& audioClipFilename)
: m_name(name),
m_imageFilename(imageFilename != "" ? new Image(imageFilename) : 0),
m_audioClipFilename(audioClipFilename != "" ? new AudioClip(audioClipFilename) : 0){}
private:
string m_name;
const auto_ptr<Image> m_imageFilename;
const auto_ptr<AudioClip> m_audioClipFilename;
};
12、禁止异常信息传递到析构函数外
当对象被删除时,即析构函数会被调用;
禁止异常信息传递到析构函数外:
- 当一个析构函数中抛出异常时,会导致程序的控制权转移到该函数之外,后调用terminate,导致局部对象没有被释放;
- 当异常抛出时,函数内部无法完成它所希望的所有事情;
Session:~Session() {
try {
// ...
}catch(...) {
// ...
}
}
13、理解抛出一个异常与传递一个参数或调用一个虚函数间的差异
在异常中参数传递内部系统过程与函数参数传递时不同的:
- 函数参数,程序的控制权最终函数会返回到函数的调用处;
- 而抛出一个异常,控制权永远不会回到抛出异常的地方;
- 异常对象在传递时总被拷贝,当传值方式被拷贝两次,对象传给函数不一定需要被拷贝;
- 异常对象类型转换比后者要少;
- 异常中允许传递一个临时对象到一个非const引用类型参数,函数中不允许;
- 抛出异常运行速度比参数参数传递慢;
13.1 要求被作为异常抛出的对象必须被复制
由于当localw离开该函数后,即被
析构
了,且改拷贝动作时通过对应类
拷贝构造来完成的;
class Widget {};
istream operator>>(istream& s, Widget& w) {
}
void passAndThrowWidget() {
Widget localW;
cin >> localW;
// throw // 捕获当前异常
throw localW; // 对localw进行拷贝动作
}
使用throw来重新抛出当前异常,不会改变被传递出去的异常类型,效率高,不会生成新拷贝;
13.2 catch中参数传递方式
# 传值捕获
catch(Widget w)
> 会建立两个被抛出对象的拷贝:
> - 所有异常都必须建立的临时对象;
> - 临时对象拷贝到w中;
# 引用捕获
catch(Widget& w)
catch(const Widget& w)
> 会建立一个被抛出对象的拷贝;
13.3 异常匹配时可以进行类型转换
- 继续类与基类之间的转换;
不要把处理基类的catch子句放在处理派生类异常的catch子句前面;
- 允许从一个类型化指针转变成无类型指针;
const void* 能捕捉任意类型的指针;
14、通过引用捕获异常
以下传递方式,没有对象拷贝;
但前提时改对象需要为全局或堆上,不能为局部变量,否则当抛出时已经被释放;
- 使用该方法能够避开删除对象释放异常;
- 避开slicing异常对象;
- 减少异常对象需要被拷贝的数目;
void func() {
static exception ex;
throw &ex;
}
void func() {
try {
//...
}catch(exception& ex) {
//
}
}
15、审慎使用异常规格
- unexpected缺省下是调用terminate,而terminate缺省下是调用abort;
- 违反异常规格:
当A调用B,B中抛出一个不在A异常规格内的异常;
- 1)避免在带有类型参数内使用异常规格;
- 2)若一个函数内调用其他没有异常规格的函数时,需去除这个函数的异常规格;
- 3)处理系统本身抛出的异常;
第2点示例
typedef void(*CallBackPtr)(int a, int b, void* args);
class CallBack{
public:
CallBack(CallBackPtr ptr, void* data)
: m_ptr(ptr),
m_data(data){}
void makeCallBack(int a, int b) const throw() {
m_ptr(a, b, m_data);
}
private:
CallBackPtr m_ptr;
void* m_data;
};
此时func抛出的异常可能超过makeCallBack能捕捉的异常;
// 改进
typedef void(*CallBackPtr)(int a, int b, void* args) throw();
16、了解异常处理的系统开销
- 程序在运行时,处理异常需要做大量的记录信息,以至于能够确保跟踪到没一个步骤;
- try模块会使代码的尺寸增加5~10%且同等的下降速度;
17、牢记80 - 20准则
- 20%的代码使用了80%的程序资源(时间、内存、磁盘访问);
- 使用profiler正确的去分析程序,从而调高效率
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)