用于大型程序的工具C++
Posted 扣得君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用于大型程序的工具C++相关的知识,希望对你有一定的参考价值。
第18章 用于大型程序的工具
此章节的主要内容分为三个部分,分别为进一步深入异常处理、命名空间、多重继承与虚继承
抛出异常
C++通过抛出(throwing)一条表达式来引发异常,当执行一个throw时,跟在throw后面的语句不再被执行,程序控制权将转移到与之匹配的catch模块。沿着调用链的函数可能会提早退出,一旦程序开始执行异常代码,则沿着调用链创建的对象将被销毁
异常捕获栈展开
如果在try内进行throw异常,则会寻找此try语句的catch是否有此异常与之匹配的捕获,如果没有将会转到调用栈的上一级,即函数调用链的上一级,try的作用域内可以有try,会向上级一层一层的找,如果到main还是不能捕获则将会除法标准库函数terminate,即程序终止
//example1.cpp
void func2()
try
throw overflow_error(" throwing a error");
cout << "3 hello world" << endl;
catch (range_error &e)
cout << "1 " << e.what() << endl;
void func1()
try
func2();
catch (overflow_error &e)
cout << "2 " << e.what() << endl;
int main(int argc, char **argv)
func1(); // 2 throwing a error
return 0;
如果异常没有被捕获,则它将终止当前的程序
栈展开与内存销毁
在栈展开时,即当throw后离开某些块作用域时,能够自动释放的栈内存将会被释放,但是要保证申请的堆内存释放,推荐使用shared_ptr与unique_ptr管理内存
//example2.cpp
class Person
public:
int age;
Person(const int &age) : age(age)
~Person()
cout << "dis Person" << endl;
;
int main(int argc, char **argv)
try
shared_ptr<Person> p(new Person(1));
throw runtime_error("m_error");
catch (runtime_error &e)
cout << e.what() << endl;
cout << "end" << endl;
//程序输出 dis Person m_error end
return 0;
析构函数与异常
可见在try作用域离开时,其内部的对象的析构函数将会被调用,但是在析构函数中也是可能存在抛出异常的情况。约定俗成,构造函数内应该仅仅throw其自己能够捕获的异常,如果在栈展开的过程中析构函数抛出了异常,并且析构函数本身没有将其捕获,则程序将会被终止
//example3.cpp
class Person
public:
~Person()
throw runtime_error("");
;
int main(int argc, char **argv)
try
Person person;
catch (runtime_error e)
cout << e.what() << endl;
//程序出发了terminate标准函数程序终止运行
return 0;
异常对象
编译器使用异常抛出表达式来对异常对象进行拷贝初始化,其确保无论最终调用的哪一个catch子句都能访问该空间,当异常处理完毕后,异常对象被销毁
如果一个throw表达式解引用基类指针,而指针实际指向派生类对象,则抛出的对象系那个会被切掉,只有基类部分被抛出
//example4.cpp
void func1()
try
runtime_error e("1");
throw e; //发生构造拷贝
catch (runtime_error &e)
cout << e.what() << endl; // 1
void func2()
try
runtime_error e("2");
throw &e; 没有被处理之前其内存不会被释放
catch (runtime_error *e)
cout << e->what() << endl; // 2
void func3()
try
runtime_error e("3");
exception *p = &e;
throw p; //只抛出基类部分
catch (exception *e)
cout << e->what() << endl;
int main(int argc, char **argv)
func1(); // 1
func2(); // 2
func3(); // 3
cout << "end" << endl; // end
return 0;
异常捕获
使用catch子句对异常对象进行捕获,如果catch无须访问抛出的表达式,则可以忽略形参的名字,捕获形参列表可以为值类型、左值引用、指针,但不可为右值引用
抛出的派生类可以对catch的基类进行初始化、如果抛出的是非引用类型、则异常对象将会切到派生类部分,最好将catch的参数定义为引用类型
catch子句的顺序
如果在多个catch语句的类型之间存在继承关系,则应该把继承链最底端的类放在前面,而将继承链最顶端的类放在后面
//example5.cpp
void func1()
try
throw runtime_error("1");
catch (exception &e)
cout << "1 exception" << endl;
catch (runtime_error &e)
cout << "1 runtime_error" << endl;
//输出 1 exception
void func2()
try
throw runtime_error("2");
catch (runtime_error &e)
cout << "2 runtime_error" << endl;
catch (exception &e)
cout << "2 exception" << endl;
// 2 runtime_error
int main(int argc, char **argv)
func1(); // 1 exception
func2(); // 2 runtime_error
return 0;
重新抛出异常
重新抛出就是在catch内对捕获的异常对象又一次抛出,有上一层进行捕获处理,重新抛出的方法就是使用throw语句,但是不包含任何表达式,空的throw只能出现在catch作用域内
//example6.cpp
void func1()
try
throw runtime_error("error");
catch (runtime_error &e)
throw; //重新抛出
void func2()
try
func1();
catch (runtime_error &e)
cout << "2 " << e.what() << endl;
int main(int argc, char **argv)
func2(); // 2 error
return 0;
捕获所有异常
有时在try代码块内有不同类型的异常对象可能被抛出,但是当这些异常发生时所需要做出的处理行为是相同的,则可以使用catch对所有类型的异常进行捕获
//example7.cpp
void func()
static default_random_engine e;
static bernoulli_distribution b;
try
bool res = b(e);
if (res)
throw runtime_error("error 1");
else
throw range_error("error 2");
catch (...)
throw;
int main(int argc, char **argv)
for (size_t i = 0; i < 10; i++)
try
func();
catch (range_error &e)
cout << e.what() << endl;
catch (...)
cout << "the error is not range_error" << endl;
// the error is not range_error
// the error is not range_error
// the error is not range_error
// error 2
// error 2
// error 2
// the error is not range_error
// error 2
// the error is not range_error
// the error is not range_error
return 0;
try与构造函数
构造函数中可能抛出异常,构造函数分为两个阶段,一个为列表初始化过程,和函数体执行的过程,但是列表初始化时产生的异常怎样进行捕获呢?
需要写成函数try语句块(也成为函数测试块,function try block)的形式
要注意的是,函数try语句块catch捕获的是列表中的错误,而不是成员初始化过程中的错误
//example8.cpp
class A
public:
shared_ptr<int> p;
A(int num)
try : p(make_shared<int>(num)) //当初始化列表中的语句执行抛出异常时
//或者函数体抛出异常时 下方catch都可以将其捕获
catch (bad_alloc &e)
cout << e.what() << endl;
;
int main(int argc, char **argv)
A a(12);
return 0;
构造函数try语句会将异常重新抛出
//example9.cpp
class A
public:
A()
try
throw runtime_error("error1");
catch (runtime_error &e)
cout << e.what() << endl;
;
int main(int argc, char **argv)
try
A a; // error1
catch (runtime_error &e)
cout << "main " << e.what() << endl; // main error1
return 0;
同样可以用于析构函数
//example10.cpp
int func()
throw runtime_error("func");
return 1;
class A
public:
int num;
A()
try : num(func())
catch (runtime_error &e)
cout << e.what() << endl; // func
~A() //可用于析构函数,捕获函数体内的异常
try
catch (...)
cout << "~A error" << endl;
;
int main(int argc, char **argv)
try
A a;
catch (runtime_error &e)
cout << "main " << e.what() << endl; // main func
return 0;
noexcept异常说明
noexcept可以提前说明某个函数不会抛出异常,可以在函数指针的声明、定义中指定noexcept。不能在typedef或类型别名中出现noexcept。在成员函数中,noexcept跟在const及引用限定符之后、在final、overrride或虚函数的=0之前
虽然指定noexcept,但是仍可以违反说明,如果违反则会触发terminate
//example11.cpp
void func() noexcept
throw runtime_error(""); // warning: throw will always call terminate()
int main(int argc, char **argv)
func();//会触发terminate
return 0;
为noexcept提供参数
noexcept(true)表示不会抛出异常、noexcept(false)表示可能抛出异常
//example12.cpp
void func() noexcept(true)
void func1() noexcept(false)
int main(int argc, char **argv)
func();
func1();
return 0;
noexcept运算符
noexcept是一个一元运算符,返回值为bool类型右值常量表达式
//example13.cpp
void func1() noexcept
void func2() noexcept(true)
void func3() noexcept(false)
throw runtime_error("");
void func4() noexcept
func1();
func2();
;
void func5(int i)
func1();
func3();
//混合使用
void func6() noexcept(noexcept(func5(9)))
int main(int argc, char **argv)
cout << noexcept(func1()) << endl; // 1
cout << noexcept(func2()) << endl; // 1
cout << noexcept(func3()) << endl; // 0
cout << noexcept(func4()) << endl; // 1
//当func4所调用的所有函数都是noexcept,且本身不含有throw时返回true 否则返回false
cout << noexcept(func5(1)) << endl; // 0
return 0;
noexcept与函数指针、虚函数
函数指针
//example14.cpp
void func() noexcept
void func1() noexcept(false)
throw runtime_error("");
void (*ptr1)() noexcept = func; // func与ptr都承诺noexcept
void (*ptr2)() = func; // func为noexcept ptr不保证noexcept
int main(int argc, char **argv)
ptr1 = func1; //不能像except的赋给nnoexcept函数指针
ptr2 = func1;
return 0;
虚函数,如果基类虚方法为noexcept则派生类派生出的也为noexcept,如果基类为except的则派生类可以指定非noexcept或者noexcept
//example15.cpp
class A
public:
virtual void f1() noexcept;
virtual void f2() noexcept(false);
virtual void f3(); //默认为noexcept(false)
;
class B : public A
public:
void f1() noexcept override
void f2() noexcept override
void f3() noexcept override
;
int main(int argc, char **argv)
B b;
b.f1(), b.f2(), b.f3();
return 0;
合成noexcept
当编译器合成拷贝控制成员时,同时会生成一个异常说明,如果该类成员和其基类所有操作都为noexcept,则合成的成员为noexcept的。不满足条件则合成noexcept(false)的。
在析构函数没有提供noexcept声明,编译器将会为其合成。合成的为与编译器直接合成析构函数提供的noexcept说明相同
常见异常类继承关系
exception只定义了拷贝构造函数、拷贝赋值运算符、虚析构函数、what的虚成员,what返回const char* 字符数组,其为noexcept(true)的
exception、bad_cast、bad_alloc有默认构造函数、runtime_error、logic_error无默认构造函数,可以接收C字符数组
编写自己的异常类
编写的异常类其的根基类为exception
//example16.cpp
class MException : public std::runtime_error
public:
int number;
MException(const string &s) : runtime_error(s), number(0)
;
int main(int argc, char **argv)
try
MException e("oop");
e.number = 999;
throw e;
catch (MException &e)
cout << e.number << endl; // 999
cout << e以上是关于用于大型程序的工具C++的主要内容,如果未能解决你的问题,请参考以下文章
C++ Primer 5th笔记(chap 18 大型程序工具)使用命名空间成员