第18章 用于大型程序的工具

18.1异常处理

try {
    // actions that cause an exception to be thrown
}
catch (...) {
    // work to partially handle the exception
    throw;
}

在C++中,通过throwing来raised一个exception。当throw时,throw后边的语句不再执行,转移到catch中,这意味:

  1. 沿着调用链的函数可能会提早退出
  2. 一点开始执行异常处理代码,沿着调用链创建的对象将被销毁
  3. 当throw一个exception时,会沿着函数调用链展开,寻找匹配的catch,如果没有找到,就会terminate。
  4. 展开过程中,对象被自动销毁
  5. 析构函数因为自动执行,所以外部无法捕获异常,他的异常应该在析构函数内部捕获并处理,如果没有处理,程序会terminate
  6. 抛出的对象如果是局部对象,将会因为展开过程中的自动销毁而不存在,抛出指针后所指向的对象必须保证存在。

18.1.2捕获异常

Catch中的异常声明,可以当成函数列表,如果使用可以为空。

Catch中的异常对象如果非引用,将会发生拷贝

特例的异常处理应该放在与异常最靠近的地方,一般的异常会截获特例

在catch异常中,一般不允许类型转换(允许非const到const、派生类到基类、数组|函数转换到指针)

在异常处理中,如果不能完全处理异常,可以再次抛出。

Catch可以处理所有异常,可以使用【…】为参数

18.1.3函数try语句块与构造函数

要想处理构造函数初始值抛出的异常,必须将构造函数写成function try blocks,因为在构造初始值时(如下初始化data的时候),并未进入函数,所以在函数中无法处理,只能使用函数try语句块。(这个异常不能处理il构造时的异常)

template <typenameT>
Blob<T>::Blob(std::initializer_list<T> il) try :
    data(std::make_shared<std::vector<T>>(il)) {
    /* empty body*/
}
catch (const std::bad_alloc &e)
{
    handle_out_of_memory(e);
}

18.1.4异常说明noexcept

  1. 如果一个函数后有noexcept,表示这个函数不会抛出异常(声明和定义中都必须出现)。亦可以在一个函数指针的声明核定一种指定noexcept。
  2. 如果函数声明为noexcept,但是确实发生了异常,就会terminal,而并不会栈展开。

判断一个调用是否是异常你一使用一元运算符noexcept(),并返回一个bool值指示是否会抛出异常。如果noexcept(true)放在一个函数后,表示此函数不会抛出异常,如果noexcept(false)放在一个函数后,表示此函数可能抛出异常。

//如果g()确定不会抛出异常,返回true,则f()确定不异常
void f() noexcept(noexcept(g()));

异常说明与指针、虚函数和拷贝控制

如果函数指针有noexcept说明符,则不能将没有noexcept声明的函数绑定到上边

如果函数指针没有noexcept说明,则可以将任何函数绑定到上边。

如果虚函数承诺了noexcept,则继承后的派生类也必须声明为noexcept

对于合成的拷贝控制成员,如果其能够确定不会抛出异常,则合成的也是noexcept

18.1.5异常类层次


exception


bad_alloc


none


logic_error


domain_error


invalid_argument


out_of_range


length_error


runtime_error


overflow_error


underflow_error


range_error


bad_cast


none

自定义异常类

class out_of_stock : public std::runtime_error
{
public:
    explicit out_of_stock(const std::string &s) :
        std::runtime_error(s) { }
};

18.2命名空间

//定义命名空间
namespace my_namespace
{
    //将其他空间中的成员在这里声明
    using std::cout;
    //命名空间别名
    namespace lib = std;
    void Foo()
    {
        //endl没有声明,必须写完整
        cout << "my namespace" << lib::endl;
    }
    //嵌套的命名空间,并且是内联的
    //内联的命名空间中的成员使用时不必完整写全
    inline namespace my_namespace_extends
    {
    }
}
class ClassName
{
};
//模板特例化必须在原命名空间中
namespace std
{
    template <> struct hash<ClassName>;
}
//未命名的命名空间
//其中变量拥有静态声明周期,第一次使用前创建,程序结束销毁
//可以在一个文件中不连续,不能跨文件
namespace
{

}

18.2.3类、命名空间和作用域

函数调用时,实参的命名空间会自动引入。

18.3多重继承与虚继承

  1. 在某个给定的派生列表中,同一个基类只能出现一次。
  2. 派生类构造函数可以初始化它的直接基类。
  3. 派生类继承基类的构造函数,但是如果有多个基类中构造函数参数相同,则必须自定义这个构造函数,否则会出错。
  4. 如果一个函数重载对每个基类作为参数,则函数在派生类的对象做参数时,类型转换会有二义性错误
  5. 虽然派生列表中,一个基类可以出现一次,但是也可以通过间接多次继承同一个类。默认情况下,多次继承的类将对应多个独立的部分,存在于继承链的不同位置。
  6. 如果希望这个继承多次的类只对应一个部分,则使用虚继承。
  7. 但是需要注意,派生类继承时,其基类所对应的同一个间接基类之间的关系是虚拟继承时,才会实现虚继承。

    class Raccoon : public virtual ZooAnimal { /* ... */ };
    class Bear : virtual public ZooAnimal { /* ... */ };
    class Panda :Raccoon, Bear {/* ... */};
  8. 派生类的Panda需要独自控制其虚拟基类部分,如果Panda没有控制,则使用其默认构造函数,并且虚拟基类会首先被构造。

    class Panda :Raccoon, Bear
    {
        Panda(std::string name, bool onExhibit)
            :ZooAnimal(name, onExhibit, "Panda"),
            Bear(name, onExhibit),
            Raccoon(name, onExhibit),
            Endangered(Endangered::critical),
            sleeping_flag(false) { }
    };
  9. 析构函数是构造函数调用顺序的反序。
时间: 2024-08-24 10:21:39

第18章 用于大型程序的工具的相关文章

C++ Primer 学习笔记_89_用于大型程序的工具 --异常处理[续2]

用于大型程序的工具 --异常处理[续2] 八.自动资源释放 考虑下面函数: void f() { vector<string> v; string s; while (cin >> s) { v.push_back(s); } string *p = new string[v.size()]; //... delete p; } 在正常情况下,数组和vector都在退出函数之前被撤销,函数中最后一个语句释放数组,在函数结束时自动撤销vector. 但是,如果在函数内部发生异常,则将

C++ Primer 学习笔记_90_用于大型程序的工具 --异常处理[续3]

用于大型程序的工具 --异常处理[续3] 九.auto_ptr类[接上] 5.auto_ptr对象的复制和赋值是破坏性操作 auto_ptr和内置指针对待复制和赋值有非常关键的区别.当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态. auto_ptr<string> strPtr1(new string("HELLO!")); auto_ptr<

C++ Primer 学习笔记_93_用于大型程序的工具 --命名空间[续2]

用于大型程序的工具 --命名空间[续2] 五.类.命名空间和作用域 名字的可见性穿过任意嵌套作用域,直到引入名字的块的末尾. 对命名空间内部使用的名字的查找遵循常规C++查找规则:当查找名字的时候,通过外围作用域外查找.对命名空间内部使用的名字而言,外围作用域可能是一个或多个嵌套的命名空间,最终以全包围的全局命名空间结束.只考虑已经在使用点之前声明的名字,而该使用仍在开放的块中: namespace A { int i; namespace B { int i; int j; int f1()

C++ Primer 学习笔记_94_用于大型程序的工具 --命名空间[续3]

用于大型程序的工具 --命名空间[续3] 六.重载与命名空间 正如我们所见,每个命名空间维持自己的作用域,因此,作为两个不同命名空间的成员的函数不能互相重载.但是,给定命名空间可以包含一组重载函数成员. 1.候选函数与命名空间 命名空间对函数匹配有两个影响.一个影响是明显的:using声明或using 指示可以将函数加到候选集合.另一个影响则微妙得多. 正如前节所见,有一个或多个类类型形参的函数的名字查找包括定义每个形参类型的命名空间.这个规则还影响怎样确定候选集合,为找候选函数而查找定义形参类

C++ Primer 学习笔记_95_用于大型程序的工具 --多重继承与虚继承

用于大型程序的工具 --多重继承与虚继承 引言: 大多数应用程序使用单个基类的公用继承,但是,在某些情况下,单继承是不够用的,因为可能无法为问题域建模,或者会对模型带来不必要的复杂性. 在这些情况下,多重继承可以更直接地为应用程序建模.多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性. 一.多重继承 1.定义多个类 为了支持多重继承,扩充派生列表: class Bear : public ZooAnimal { //... }; 以支持由逗号分隔的基类列表: cla

C++ Primer 学习笔记_96_用于大型程序的工具 --多重继承与虚继承[续1]

用于大型程序的工具 --多重继承与虚继承[续1] 四.多重继承下的类作用域 成员函数中使用的名字和查找首先在函数本身进行,如果不能在本地找到名字,就继续在本类中查找,然后依次查找每个基类.在多重继承下,查找同时检察所有的基类继承子树 -- 在我们的例子中,并行查找 Endangered子树和Bear/ZooAnimal子树.如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的. [小心地雷] 当一个类有多个基类的时候,通过对所有直接基类同时进行名字查

C++ Primer 学习笔记_87_用于大型程序的工具 --异常处理

用于大型程序的工具 --异常处理 引言: C++语言包括的一些特征在问题比較复杂,非个人所能管理时最为实用.如:异常处理.命名空间和多重继承. 相对于小的程序猿团队所能开发的系统需求而言,大规模编程[往往涉及数千万行代码]对程序设计语言的要求更高.大规模应用程序往往具有下列特殊要求: 1.更严格的正常运转时间以及更健壮的错误检測和错误处理.错误处理常常必须跨越独立开发的多个子系统进行[异常处理]. 2.能够用各种库(可能包括独立开发的库)构造程序[命名空间]. 3.能够处理更复杂的应用概念[多重

C++ Primer 学习笔记_97_用于大型程序的工具 --多重继承与虚继承[续2]

用于大型程序的工具 --多重继承与虚继承[续2] 七.特殊的初始化语义 从具有虚基类的类继承的类对初始化进行特殊处理:在虚基类中,由最低层派生类的构造函数初始化虚基类.在ZooAnimal示例中,使用常规规则将导致Bear 类和 Raccoon类都试图初始化Panda对象的ZooAnimal类部分. 虽然由最低层派生类初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式.只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化只在创建中间类型

C++ Primer 学习笔记_91_用于大型程序的工具 --命名空间

用于大型程序的工具 --命名空间 引言: 在一个给定作用域中定义的每个名字在该作用域中必须是唯一的,对庞大.复杂的应用程序而言,这个要求可能难以满足.这样的应用程序的全局作用域中一般有许多名字定义.由独立开发的库构成的复杂程序更有可能遇到名字冲突 -- 同样的名字既可能在我们自己的代码中使用,也可能(更常见地)在独立供应商提供的代码中使用. 库倾向于定义许多全局名字 -- 主要是模板名.类型名或函数名.在使用来自多个供应商的库编写应用程序的时候,这些名字中有一些几乎不可避免地会发生冲突,这种名字