(1)用于处理程序在调用过程中的非正常行为
1 传统的处理方法:传返回值表示函数调用是否正常结束
2 C++ 中的处理方法:通过关键字 try/catch/throw 引入异常处理机制
(2)异常触发时的系统行为:栈展开
1 抛出异常后续的代码不会被执行
2 局部对象会按照构造相反的顺序自动销毁
3 系统尝试匹配相应的 catch 代码段
如果匹配则执行其中的逻辑,之后执行 catch 后续的代码
如果不匹配则继续进行栈展开,直到 跳出 main 函数,触发 terminate 结束运行
#include
void f1()
{
throw 1;
}
void f2()
{
int x2;
try
{
f1();
}
catch (int)
{
std::cout << "exception is catched in f2\n";
}
std::cout << "other logic in f2\n";
}
void f3()
{
f2();
}
int main()
{
try
{
f3();
}
catch (int)
{
std::cout << "exception is catched in main\n";
}
}
(3)异常对象
1 系统会使用抛出的异常拷贝初始化一个临时对象,称为异常对象
2 异常对象会在栈展开过程中被保留,并最终传递给匹配的 catch 语句
(4)try / catch 语句块
1 一个 try 语句块后面可以跟一到多个 catch 语句块
2 每个 catch 语句块用于匹配一种类型的异常对象
3 catch 语句块的匹配按照从上到下进行
4 使用 catch(…) 匹配任意异常
5 在 catch 中调用 throw 继续抛出相同的异常
(5)在一个异常未处理完成时抛出新的异常会导致程序崩溃
1 不要在析构函数或 operator delete 函数重载版本中抛出异常
2 通常来说, catch 所接收的异常类型为引用类型
(6)异常与构造、析构函数
1 使用 function-try-block保护初始化逻辑
#include
struct Str
{
Str()
{
throw 100;
}
};
class Cla
{
public:
Cla()
try : m_mem()
{
}
catch (int)
{
std::cout << "Exception is called at Cla::Cla." << std::endl;
}
private:
Str m_mem;
};
int main()
{
try
{
Cla obj;
}
catch (int)
{
std::cout << "Exception is called at main." << std::endl;
}
}
#include
struct Str
{
Str()
{
throw 100;
}
};
class Cla
{
public:
Cla()
try : m_mem()
{
}
catch (int)
{
std::cout << "Exception is called at Cla::Cla." << std::endl;
}
private:
Str m_mem;
};
void fun(Str x)
try
{
}
catch (...)
{
std::cout << 123 << std::endl;
}
int main()
{
try
{
fun(Str());
}
catch (int)
{
std::cout << 456 << std::endl;
}
}
#include
void fun()
try
{
throw 12;
}
catch (...)
{
std::cout << 123 << std::endl;
}
int main()
{
fun();
}
2 在构造函数中抛出异常:
已经构造的成员会被销毁,但类本身的析构函数不会被调用
(7)描述函数是否会抛出异常
1 如果函数不会抛出异常,则应表明以为系统提供更多的优化空间
C++ 98 的方式: throw() / throw(int, char) --编译期
C++11 后的改进: noexcept / noexcept(false) --运行期
2 noexcept
限定符:接收 false / true 表示是否会抛出异常
操作符:接收一个表达式,根据表达式是否可能抛出异常返回 false/true
在声明了 noexcept 的函数中抛出异常会导致 terminate 被调用,程序终止
不作为函数重载依据,但函数指针、虚拟函数重写时要保持形式兼容
#include
void fun() //noexcept
{
}
int main()
{
std::cout << noexcept(fun()) << std::endl;
}
#include
void fun() noexcept
{
}
int main()
{
void (*ptr)() noexcept = fun;
(*ptr)();
}
#include
class Base
{
public:
virtual void fun() noexcept(false)
{}
};
class Derive : public Base
{
public:
void fun() noexcept override
{}
};
int main()
{
Derive d;
Base& ref = d;
ref.fun();
}
(8)标准异常
(9)正确对待异常处理
不要滥用:异常的执行成本非常高
不要不用:对于真正的异常场景,异常处理是相对高效、简洁的处理方式
编写异常安全的代码
#include
#include
#include
void fun(int x)
{
std::unique_ptr<int> ptr = std::make_unique<int>(3);
throw 1234;
}
int mian()
{
try
{
fun(100);
}
catch (int& e)
{
std::cout << e << std::endl;
}
}
(1)枚举(enum) :一种取值受限的特殊类型
1 分为无作用域枚举与有作用域枚举( C++11 起)两种
#include
enum Color
{
Red,
Green,
Yellow
};
int main()
{
Color x = Red;
}
#include
enum class Color
{
Red,
Green,
Yellow
};
int main()
{
Color x = Color::Red;
}
2 枚举项缺省使用 0 初始化,依次递增,可以使用常量表达式来修改缺省值
#include
enum class Color
{
Red,
Green,
Yellow
};
int main()
{
Color x = Color::Red;
std::cout << x << std::endl;
Color y = Color::Green;
std::cout << y << std::endl;
}
3 可以为枚举指定底层类型,表明了枚举项的尺寸
#include
enum Color : char
{
Red,
Green,
Yellow
};
int main()
{
std::cout << sizeof(Color) << std::endl;
}
4 无作用域枚举项可隐式转换为整数值;也可用 static_cast 在枚举项与整数值间转换
#include
enum class Color
{
Red,
Green,
Yellow
};
void fun(Color x)
{
}
int main()
{
fun(static_cast<Color>(100));
std::cout << static_cast<int>(Color::red) << std::endl;
}
5 注意区分枚举的定义与声明
#include
enum cColor;
int main()
{
Color x;
}
#include
enum Color : int;
int main()
{
Color x;
}
(2)联合( union ):将多个类型合并到一起以节省空间
#include
union Str
{
int x;
int y;
};
int main()
{
std::cout << sizeof(Str) << std::endl;
Str obj;
obj.x = 100;
std::cout << obj.y << std::endl;
}
1 通常与枚举一起使用
#include
enum Type
{
Char,
In
};
union Str
{
char x;
int y;
};
struct S{
Type t;
Str obj;
};
int main()
{
std::cout << sizeof(Str) << std::endl;
S s;
s.t = Char;
s.obj.x = 'c';
}
2 匿名联合
#include
struct S{
enum Type
{
Char,
In
};
union
{
char x;
int y;
};
Type t;
};
int main()
{
std::cout << sizeof(Str) << std::endl;
S s;
s.t = S::Char;
s.x = 'c';
}
3 在联合中包含非内建类型( C++11 起)
(1)嵌套类:在类中定义的类
1 嵌套类具有自己的域,与外围类的域形成嵌套关系
嵌套类中的名称查找失败时会在其外围类中继续查找
2 嵌套类与外围类单独拥有各自的成员
#include
class Out
{
using MyInt = int;
inline static int val2 = 4;
public:
class In
{
public:
inline static int val = val2;
};
};
int main()
{
Out::In::val;
}
(2)局部类:可以在函数内部定义的类
1 可以访问外围函数中定义的类型声明、静态对象与枚举
2 局部类可以定义成员函数,但成员函数的定义必须位于类内部
3 局部类不能定义静态数据成员
#include
void fun()
{
using MyInt = int;
struct Helper
{
MyInt x;
int y;
int inc()
{
return x++;
}
};
Helper h;
h.inc();
}
int main()
{
fun();
}
(1)嵌套名字空间
1 名字空间可以嵌套,嵌套名字空间形成嵌套域
2 注意同样的名字空间定义可以出现在程序多处,以向同一个名字空间中增加声明或定义
3 C++17 开始可以简化嵌套名字空间的定义
#include
namespace Out
{
int y;
}
namespace Out::In
{
int z;
}
int main()
{
Out::In::z;
}
(2)匿名名字空间
1 用于构造仅翻译单元可见的对象
2 可用 static 代替
3 可作为嵌套名字空间
#include
namespace
{
int y;
}
int main()
{
}
#include
namespace MyNs
{
namespace
{
int y;
}
}
int main()
{
MyNs::y = 3;
}
(1)位域:显示表明对象尺寸(所占位数)
1 在结构体 / 类中使用
2 多个位域对象可能会被打包存取
3 声明了位域的对象无法取地址,因此不能使用指针或非常量引用进行绑定
4 尺寸通常会小于对象类型所对应的尺寸,否则取值受类型限制
#include
struct A
{
bool b1 : 1;
bool b2 : 1;
};
int main()
{
std::cout << sizeof (A) << std::endl;
}
#include
struct A
{
char b : 1000;
};
int main()
{
std::cout << sizeof (A) << std::endl;
}
(2)volatile 关键字
1 表明一个对象的可能会被当前程序以外的逻辑修改
2 相应对象的读写可能会加重程序负担
3 注意慎重使用 一些情况下可以用 atomic 代替