c++ primer 18th 用于大型程序的工具

一、异常处理
  1. 抛出异常

    当执行一个throw时,跟在throw后面的语句将不再执行。相反,程序的控制权从throw转移到与之匹配的catch模块。该catch可能是同一个函数的局部catch,也可能位于直接或间接调用了发生异常的函数的另一个函数中。控制权从一处转移到另一处,这有两个重要的含义:

    • 沿着调用链的函数可能会提早退出
    • 一旦程序开始执行异常处理代码,则沿着调用链创建的对象将被销毁
  2. 栈展开

    当抛出一个异常后,程序暂停当前函数的执行过程并立即开始寻找与异常匹配的catch字句。如果对抛出异常的函数的调用位于try语句块内,则检查与该try块关联的catch子句。如果找到了匹配的catch,就使用该catch处理异常。否则,如果该try语句嵌套在其他try块中,则继续检查与外层try匹配的catch字句。如果仍然没有找到匹配的catch,则退出当前这个主调函数,继续在调用了刚刚退出的这个函数的其他函数中寻找,以此类推。

    上述过程被称为栈展开过程。栈展开过程沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子句为止;或者也可能一直没找到匹配的catch,则退出主函数后查找过程终止。

    如果没找到匹配的catch子句,程序将退出。因为异常通常被认为是妨碍程序正常执行的事件,所以一旦引发了某个异常,就不能对它置之不理。当找不到匹配的catch时,程序将调用terminate

    一个异常如果没有被捕获,则它将终止当前的程序。

    栈展开过程中对象被自动销毁:如果在栈展开过程中退出了某个块,编译器将负责确保在这个块中创建的对象能被正确地销毁。如果某个局部对象的类型是类类型,则该对象的析构函数将被自动调用。与往常一样,编译器在销毁内置类型的对象时不需要做任何事情。(析构函数仅仅是释放资源,一定要确保它们的析构函数不会引发异常)

  3. 异常对象

    当我们抛出一条表达式时,该表达式的静态编译时类型决定了异常对象的类型。如果一条throw表达式解引用一个基类指针,而给指针实际指向的是派生类对象,则抛出的对象将被切掉一部分。

  4. 查找匹配的处理代码

    与实参和形参的匹配规则相比,异常和catch异常声明的匹配规则受到更多限制。此时,绝大多数类型转换都不被允许,要求异常的类型和catch声明的类型是精确匹配的:

    • 允许从非常量向常量的类型转换,也就是说,一条非常量对象的throw语句可以匹配一个接受常量引用的catch语句。
    • 允许从派生类向基类的类型转换。
    • 数组被转换成指向数组类型的指针,函数被转换指向该函数类型的指针。
  5. 重新抛出

    有时,一个单独的catch语句不能完整地处理某个异常。在执行了某些校正操作之后,当前的catch可能会决定由调用链更上一层的函数接着处理异常。

    这里的重新抛出仍然是一条throw语句,只不过不包含任何表达式:

    throw;
    
  6. 捕获所有异常的处理代码

    void mainp()
    {
    	try
    	{
    	
    	}
    	catch(...)
    	{
    	
    	}
    }
    

    catch(...)既能单独出现,也能与其他几个catch语句一起出现。

    如果catch(...)与其他几个catch语句一起出现,则catch(...)必须在最后的位置。出现在捕获所有异常语句后面的catch语句将永远不会被匹配。

  7. 虚函数处理

    如果一个虚函数承诺了它不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺;与之相反,如果基类的虚函数允许抛出异常,则派生类的对应函数既可以允许抛出异常,也可以不允许抛出异常。

    class Base
    {
    public:
        virtual double f1(double) noexcept;
        virtual int f2() noexcept(false);
        virtual void f3();
    };
    class Derived : public Base
    {
    public:
        double f1(double); //错误
        int f2() noexcept(false);
        void f3() noexcept;
    };
    
  8. 异常类层次

二、命名空间
  1. 未命名的命名空间

    #include<bits/stdc++.h>
    using namespace std;
    namespace 
    {
        int i;
    }
    namespace local
    {
        namespace
        {
            int i;
        }
    }
    int main()
    { 
        i = 0;
        local::i = 1;
        cout << i << endl;
        cout << local::i << endl;
    }   
    
  2. using指示与作用域

    #include<bits/stdc++.h>
    using namespace std;
    namespace blip
    {
        int i = 16,j = 15,k = 23;
    }
    int j = 0;
    int main()
    {
        using namespace blip;
        ++i;
        //++j;//错误
        ++::j;
        ++blip::j;
        int k = 97;
        ++k;
    }
    
  3. 有元声明与实参相关的查找

    #include<iostream>
    using namespace std;
    namespace A
    {
        class C
        {
            friend void f2();
            friend void f(const C&);
        };
    }
    int main()
    {
        A::C cobj;
        f(cobj);
        f2(); //错误
    }
    

    因为f接受一个类类型的实参,而且f在C所属的命名空间进行了隐式的声明,所以f能被找到。相反,因为f2没有形参,所以它无法被找到。

  4. 重载与命名空间

    1. 与实参相关的查找与重载

      #include<iostream>
      using namespace std;
      namespace NS
      {
          class Quote
          {
      
          };
          void display(const Quote&)
          {
      
          }
      }
      class Bulk_item : public NS::Quote
      {
      
      };
      int main()
      {
          Bulk_item book1;
          display(book1);
          return 0;
      }
      
    2. 重载与using声明

      using声明语句声明的是一个名字,而非一个特定的函数

      using NS::print(int);//错误
      using NS::print;
      
    3. 跨越多个using指示的重载

      #include<iostream>
      using namespace std;
      namespace AW
      {
          int print(int);
      }
      namespace Primer
      {
          double print(double);
      }
      using namespace AW;
      using namespace Primer;
      long double print(long double);
      int main()
      {
          print(1); //AW
          print(3.1);//Primer
      }
      
三、多重继承与虚继承
  1. 继承的构造函数与多重继承

    允许派生类从它的一个或几个基类中继承构造函数。但是如果从多个基类中继承了相同的构造函数,则程序将产生错误。

    struct Base1
    {
        Base1() = default;
        Base1(const string&);
    };
    struct Base2
    {
        Base2() = default;
        Base2(const string&);
    };
    struct D1 : public Base1 , public Base2{
        using Base1::Base1;
        using Base2::Base2;
    };
    int main()
    {
        D1 d("123");  //错误 都继承D1::D1(const string&)
    }
    
    #include<iostream>
    using namespace std;
    struct Base1
    {
        Base1() = default;
        Base1(const string&)
        {
    
        }
    };
    struct Base2
    {
        Base2() = default;
        Base2(const string&)
        {
    
        }
    };
    struct D2:public Base1,public Base2
    {
        using Base1::Base1;
        using Base2::Base2;
        D2(const string&s):Base1(s),Base2(s)
        {
    
        }
        //D2() = default;//一旦D2定义它自己的构造函数,则必须出现
    
    };
    int main()
    {
        D2 d("123");
        return 0;
        //cout << "hello world" << endl;      
    }
    
    
  2. 基于指针类型或引用类型的查找

    #include<iostream>
    using namespace std;
    class Bear
    {
    public:
        Bear()
        {
            cout << 1 << endl;
        }
        void print()
        {
            cout << 4 << endl;
        }
    };
    class Panda : public Bear
    {
    public:
        Panda()
        {
            cout << 2 << endl;
        }
        void print()
        {
            cout << 3 << endl;
        }
    };
    int main()
    {
        Bear *b = new Panda();
        b->print();
    }
    //1 2 4
    

    与只有一个基类的继承一样,对象、指针和引用的静态类型决定了我们能够使用哪些成员。如果我们使用一个Bear指针,则只有定义在Bear中的操作是可以使用的,Panda接口中的Bear特有的部分都不可见。类似一个派生类指针或引用只能访问自身和基类的成员。

  3. 多重继承下的类作用域

    class Bear
    {
    public:
        Bear()
        {
            cout << 1 << endl;
        }
        void print()
        {
            cout << 4 << endl;
        }
        int age;
    };
    class ZooAnimal
    {
    public:
        int age;
    };
    class Panda : public Bear,public ZooAnimal
    {
    public:
        Panda()
        {
            cout << 2 << endl;
        }
        void print()
        {
            cout << 3 << endl;
        }
        int max_age()
        {
            return max(ZooAnimal::age,Bear::age);
        }
    };
    
  4. 虚继承

    虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生类层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。

    必须在虚派生的真实需求出现前就完成虚派生的操作。

    虚派生只影响从指定了虚基类的派生类中进一步派生出的类,它不会影响派生类本身。

  5. 使用虚基类

    class Raccoon : public virtual ZooAnimal {};
    class Bear : virtual public ZooAnimal {};
    

    virtual说明符表明了一种愿望,即在后续的派生类当中共享虚基类的同一份实例。至于什么样的类能够作为虚基类并没有特殊规定。

    如果某个类指定了虚基类,则该类的派生仍按常规方式进行:

    class Panda: public Bear,public Raccoon, public Endangered{}
    
    #include<bits/stdc++.h>
    using namespace std;
    class ZooAnimal
    {
    public:
        void print1()
        {
            cout << 1 << endl;
        }
    };
    class Bear : public virtual ZooAnimal
    {
    public:
        void print2()
        {
            cout << 2 << endl;
        }
    };
    class Panda : public Bear
    {
    public:
        void print3()
        {
            cout << 3 << endl;
        }
    };
    int main()
    {
        Panda *b = new Panda();
        b->print1();
        b->print2();
        b->print3();
    }
    
  6. 虚基类成员的可见性

    因为在每个共享的虚基类中只有唯一一个共享的子对象,所以该基类的成员可以被直接访问,并且不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,则我们仍然可以直接访问这个被覆盖的成员。但是如果成员被多于一个基类覆盖,则一般情况下派生类必须为该成员自定义一个新的版本。

    例如,假定类B定义了一个名为x的成员,D1和D2都是从B虚继承得到的,D继承了D1和D2,则在D的作用域中,x通过D的两个基类都是可见的。如果我们通过D的对象使用x,有三种可能性:

    • 如果在D1和D2中都没有x的定义,则x将被解析为B的成员,此时不存在二义性,一个D的对象只含有x的一个实例。
    • 如果x是B的成员,同时是D1和D2中某一个的成员,则同样没有二义性,派生类的x比共享虚基类B的x优先级更高。
    • 如果在D1和D2中都有x的定义,则直接访问x将产生二义性问题。
  7. 当创建Panda对象的时候:
    (1)首先使用构造函数初始化列表中指定的初始化式构造ZooAnimal部分。
    (2)接下来,构造Bear部分。忽略Bear的用于ZooAnimal构造函数初始化列表的初始化式。
    (3)然后,构造Raccoon部分。再次忽略ZooAnimal初始化式。
    (4)最后,构造Panda部分。
    如果Panda构造函数不显式初始化ZooAnimal基类,就使用ZooAnimal默认构造函数;如果ZooAnimal没有默认构造函数,则代码出错。

  8. 无论虚基类出现在继承层次中任何地方,总是在构造非基类之前构造虚基类:

    class Character{ /*....*/ };
    class BookCharacter: public Character{ /*....*/ };
    class ToyAnimal{ /*...*/ };
    class TeddyBear: public BookChatacter,public Bear,public virtual ToyAnimal{ /*...*/ };
    

    按声明次序检查直接基类,确定是否在虚基类。例中,首先检查BookChatacter的继承子树,然后检查Bear的继承子树,最后检查ToyAnimal的继承子树。按从根类开始向下倒最低层派生类的次序检查每个子树。
    TeddyBear的虚基类的构造次序是先ZooAnimal再ToyAnimal。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是BookChatacter,它导致调用Character的构造函数,然后是Bear。因此,为了创建TeddyBear对象,按下面次序调用构造函数:

    • ZooAnimal();
    • ToyAnimal();
    • Character();
    • BookChatacter();
    • Bear();
    • TeddyBear();

    在这里,由最低层派生类TeddyBear指定用于ZooAnimal和ToyAnimal的初始化式。

原文地址:https://www.cnblogs.com/Jawen/p/11158351.html