大规模应用程序的特殊要求包括:

  • 在独立开发的子系统之间协同处理错误的能力。
  • 使用各种库进行协同开发的能力。
  • 对比较复杂的应用概念建模的能力。

    异常处理

    异常处理(exception handling)机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并作出相应的处理。

    抛出异常

    在C++语言中,我们通过抛出(throwing)一条表达式来引发(raised)一个异常。异常类型和当前的调用链决定了哪段处理代码(handler)将用来处理该异常。
    程序的控制权从throw转移到catch模块。
    栈展开:当throw出现在一个try语句块时,检查该try语句块相关的catch字句,若有匹配则处理;若无匹配,则继续检查外层的try匹配的catch
    若一个异常没有被捕获,则它将终止当前的程序。
    对象销毁:

  • 块退出后,它的局部对象将被销毁。

  • 若异常发生在构造函数中,即使某个对象只构造了一部分,也要确保已构造的成员正确地被销毁。
  • 将资源释放放在类的析构函数中,以保证资源能被正确释放。析构函数本身不会引发异常。

    捕获异常

    若无需访问抛出的异常对象,则可以忽略捕获形参的名字。
    通常,若catch接受的异常与某个继承体系有关,则最好将该catch的参数定义成引用类型。
    搜索catch未必是最佳匹配,而是第一个匹配,因此,越细化的catch越应该放在catch列表前段。
    重新抛出:catch代码执行一条throw;将异常传递给另一个catch语句。
    捕获所有异常:catch(...)

    构造函数

    处理构造函数初始值异常的唯一方法是将构造函数协程函数try语句块。
    示例:

    1. template <typename T>
    2. Blob<T>::Blob(std::initializer_list<T> il) try:
    3. data(std::make_shared<std::vector<T> >(il)
    4. {
    5. /*函数体*/
    6. } catch(const std::bad_alloc &e)
    7. {
    8. handle_out_of_memory(e);
    9. }

    noexcept异常说明

    使用noexcept说明指定某个函数不会抛出异常。
    示例:

    1. void recoup(int) noexcept; //C++11
    2. coid recoup(int) throw(); //老版本

    异常类层次

    标准exception层次:

  • exception

    • bad_cast
    • bad_alloc
    • runtime_error
      • overflow_error
      • underflow_error
      • range_error
    • logic_error
      • domain_error
      • invalid_argument
      • out_of_range
      • length_error

        自定义异常类

        示例: ```cpp

        include

        include

        include

//自定义异常类 class DivisionByZeroException : public std::exception { public: DivisionByZeroException() = delete; //无默认构造函数 explicit DivisionByZeroException(const int dividend) { errMsg = “It is not allowed to deviend “ + std::to_string(dividend) + “ by 0”; }

  1. virtual const char* what() const noexcept override
  2. {
  3. return errMsg.c_str();
  4. }

private: std::string errMsg; };

int main() { int dividend = 10, divior = 0; try { if (divior == 0) throw DivisionByZeroException(dividend); else std::cout << dividend / divior; } catch (DivisionByZeroException& ex) { std::cout << ex.what() << std::endl; } return 0; }

  1. <a name="OKhO5"></a>
  2. ## 命名空间
  3. 多个库将名字放置在全局命名空间中将引发**命名空间污染**(namespace pollution)。**命名空间**(namespace)分割了全局命名空间,其中每个命名空间是一个作用域。
  4. <a name="J4ed8"></a>
  5. ### 命名空间定义
  6. 命名空间的定义包含两部分:1.关键字`namespace`;2.命名空间名称。后面是一系列由花括号括起来的声明和定义。命名空间作用域后面无需分号。<br />示例:
  7. ```cpp
  8. namespace cplusplus_primer{
  9. }

每个命名空间都是一个作用域。定义在某个命名空间内的名字可以被该命名空间内的其他成员直接访问,也可以被这些成员内嵌套作用域中的任何单位访问。位于该命名空间之外的代码必须明确指出所用的名字是属于哪个命名空间的。
命名空间可以是不连续的。这点不同于其他作用域,意味着同一命名空间可以在多处出现。
内联命名空间(C++11):
无需使用该命名空间的前缀,通过外层命名空间就可以直接访问。
示例:

  1. namespace cplusplus_primer{
  2. inline namespace FifthEd{
  3. // 表示本书第5版代码
  4. class Query_base {};
  5. }
  6. }
  7. cplusplus_primer::Query_base qb;

未命名的命名空间
指关键字namespace后面紧跟花括号的用法。未命名的命名空间中定义的变量拥有静态的声明周期:在第一次使用前创建,直到程序结束才销毁。不能跨越多个文件。

使用命名空间成员

namespace_name::member_name这样使用命名空间的成员非常繁琐。
命名空间的别名

  1. namespace primer = cplusplus_primer;

using声明(using declaration):
一条using声明语句一次只引入命名空间的一个成员。

  1. using std::string;
  2. string s = "hello";

using指示(using directive):
使得某个特定的命名空间中所有的名字都可见。

  1. using namespace std;
  2. string s = "hello";

类、命名空间与作用域

  1. namespace A{
  2. class C1{
  3. public:
  4. int f3();
  5. }
  6. }
  7. A::C1::f3

重载与命名空间

using声明语句声明的是一个名字,而非特定的函数,也就是说包括该函数的所有版本,都被引入到当前作用域中。

多重继承与虚继承

多重继承

类型转换与多个基类

多重继承下的类作用域

虚继承

构造函数与虚继承