1.异常处理
try
{
try
{
}
catch(bad_alloc) //捕获特定异常
{
/**处理异常**/
throw; //或者抛出,让外层的异常捕获处理
}
}
catch(...) //捕获所有内存未处理的异常
{
/**处理异常**/
}
1.3.函数try语句块与构造函数
构造函数在进入其函数体之前首先执行初始值列表。如果在函数体内部捕获异常,因为在初始值列表抛出异常时构造函数体内的try
语句块还未生效,所以构造函数体内的catch语句无法处理构造函数初始值列表抛出的异常。
函数try语句块使得一组catch语句既能处理构造函数体(或析构函数体),也能处理构造函数的初始化过程(或析构函数的析构过程)。
例如:
template<typename T>
Test<T>::Test(std::initializer_list<T> il) try:vec(il)
{
/**空函数体**/ }
catch(const std::bad_alloc &e)
{
/**处理异常**/ }
还有一种情况值得注意,在初始化构造函数的参数时也可能发生异常,这样的异常不属于函数try语句块的一部分。函数try语句块
只能处理构造函数开始执行后发生的异常。和其他函数调用一样,如果在参数初始化的过程中发生了异常,则该异常属于调用表达式
的一部分,并将在调用者所在的上下文中处理。
1.4.noexcept异常说明
对于用户即编译器来说,预先指定某个函数不会抛出异常显然大有裨益。
首先,知道函数不会抛出异常有助于简化调用该函数的代码;
其次,如果编译器确认函数不会抛出异常,它就能执行某些特殊的优化操作。
void fun()const & noexcept override {
}
在typedef或类型别名中则不能出现noexcept.
在成员函数中,noexcept说明符需要跟在const及引用限定符后,而在final、override或虚函数=0之前。
所以的声明和定义都要有noexcept,或者都没有。
noexcept可以用在两种情况下:
1.确认函数不会抛出异常。
2.根本不知道如何处理异常。
编译器并不会在编译时检查noexcept说明。
下面两个函数等价:都不会抛出异常
void fun() noexcept;
void fun() throw(); //旧版本
异常说明的实参
noexcept说明符接受一个可选的实参,该实参必须能转换为bool类型。
void fun() noexcept(true); //不抛出异常
noexcept运算符
noexcept运算符是一个一元运算符,返回值是一个bool类型的右值常量表达式,表示给定的表达式是否会抛出异常。
和sizeof类似,noexcept也不会求运算对象的值。
noexcept(e)
当e调用的所有函数都做了不抛出说明且e本身不含有throw语句时,上述表达式为true,否则返回false。
void f() noexcept(noexcept(g())); //外层的noexcept是异常说明符,内层的是操作符。
f和g的异常说明一致:要异常都异常,不异常都不异常。
异常说明与指针、虚函数和拷贝控制
尽管noexcept说明符不属于函数类型的一部分,但是函数的异常说明仍然会影响函数的使用。
函数指针及该指针所指的函数必须具有一致的异常说明。也就是说,如果我们为某个指针做了不抛出异常的说明,则该指针将只能
指向不抛出异常的函数。如果显示或隐式说明了指针可能抛出异常,则该指针可以指向任何函数。
如果一个虚函数承诺它不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺。
如果基类的虚函数允许抛出异常,则派生类的对应函数既可以抛出异常,也可以不抛出异常。
当编译器合成拷贝控制成员时,同时也生成一个异常说明。如果对所有成员和基类的所有操作都承诺不会抛出异常,则合成的成员
是noexcept的。如果合成成员调用的任意一个函数可能抛出异常,则合成的成员是noexcept(false).而且,如果我们定义了一个
析构函数但是没有为它提供异常说明,则编译器将合成一个。合成的异常说明将与假设由编译器为类合成析构函数时所得的异常
说明一致。
1.5.异常类层次
可以定义自己的异常类
class MyExcept:public std::runtime_error
{
public:
explicit MyExcept(const std::string& s):std::runtime_error(s){
}
}
class MyExcept2:public std::logic_error
{
public:
explicit MyExcept(const std::string& s,const std::string& s2):std::runtime_error(s),str(s2){
}
const std::string str;
}
2.命名空间
#include <string>
namespace ns
{
namespace ns2{
}
inline namespace ns1{
} //内联命名空间,外层作用域可直接访问内部的成员
namespace{
} //未命名命名空间,可直接访问成员,每个文件的未命名命名空间不同
}
namespace myns = ns::ns2; //简化命名空间命名
using声明:扼要概述
一条using声明语句一次只引入命名空间的一个成员。
using ns::meb;
using指示:把命名空间中的成员注入外层作用域中
using namesapce ns; //ns命名空间所有的名字都可见,这样就无须添加任何前缀限定符了
3.多重继承与虚继承
class Base{
}
class Derived1:public virtual Base{
} //虚继承
class Derived2:public virtual Base{
} //虚继承
class SubDerived:public Derived1,public Derived2 //SubDerived中只有一份Base成员
{
public:
SubDerived():Base(),Derived1(),Derived2(){
} //直接调用虚基类的构造函数,进行初始化
}
初始化:先初始化虚基类的成员(调用虚基类的构造函数),在顺序初始化其他直接基类。
析构:最后执行虚基类的析构函数。