【C++基础】自定义异常类与多重捕获

目录

  • 自定义异常类
    • 构建过程
    • 例:Vec3D类的数组下标越界的异常类
  • 捕获多种无关异常
    • 不同的异常的捕获
    • 捕获派生异常
    • 异常处理的次序
    • 例子:多重捕获异常类
    • catch块的参数类型可以不用引用类型吗?

自定义异常类

自定义异常类通常由exception或其后代类派生。这样我们就可以去override e.what() 函数。

构建过程

这里我们以构建一个Vec3D类的数组下标越界的异常类为例子。
在这里插入图片描述

自定义异常类过程:
1、找一个高级一点的类,继承一下
class RangeException : public out_of_range
2、定义一些能够记录异常问题的变量
size_t dimension;
int index;
3、由异常类来记录问题,所以要将变量放到异常类的构造函数中,
RangeException(size_t dimension, int index);
4、给父类加入提示信息,在异常类的构造函数的初始化列表上对父类的构造函数也进行初始化。
注意exception基类构造函数是不接受参数的,其派生类可以接受一个字符串参数,用来描述该异常
RangeException () : out_of_range(“Vec index error”) {};

例:Vec3D类的数组下标越界的异常类

task1:创建Vec3D类,用array保存向量成员
task2:创建RangeException类,定义构造函数
RangeException(std::size_t dimension,const int index)
task3:实现Vec3D::operator[](const int index)
当index越界时,抛出RangeException的对象
task4:在主函数创建Vec3D对象并调用[]制造越界问题,捕获异常并输出异常中的信息。

RangeException.h:

#pragma once#include <iostream>
#include <exception>
class RangeException : public std::exception {
private:std::size_t dimension{ 3 };int index{ 0 };
public:RangeException (std::size_t dimension,const int index) {this->dimension = dimension;this->index = index;}std::size_t getDimension() {return dimension;}int getIndex() {return index;}
};

Vec3D.h:
这里需要注意一个点:数组下标运算符不加&时,返回的是一个右值,不能通过数组下标运算符修改数组的值。加上&后,返回的就是一个左值了。

#pragma once
#include <array>
#include "RangeException.h"
class Vec3D {
private:std::array <double, 3> v{1.0,1.0,1.0};
public:Vec3D() = default;Vec3D(double x, double y, double z){v[0] = x;v[1] = y;v[2] = z;}double &operator [] (const int index) {if(index >=0 && index <= 2) {return v[index];}else {throw RangeException(3,index);}}};

当主函数为:

#include <iostream>
#include "Vec3D.h"
using namespace std;int main()
{Vec3D v1 {1.2,1.3,1.4};cout << v1[4];return 0;
}

可以看见,抛出了我们自定义的异常。
在这里插入图片描述
接下来我们捕获这个异常:
注意,此时我们是捕获exception基类类型的异常,由于RangeException是从exception继承下来的,所以我们能够捕获,但是我们没有对exception的what进行覆写,所以不会打印异常信息。

#include <iostream>
#include <exception>
#include "Vec3D.h"
using namespace std;int main()
{Vec3D v1 {1.2,1.3,1.4};try {cout << v1[4];}catch (exception & e) {cout << "Exception :" << e.what() << endl;}return 0;
}

在这里插入图片描述
接下来我们将exception转换为RangeException,再调用RangeException的成员函数进行异常信息查询。

int main()
{Vec3D v1 {1.2,1.3,1.4};try {cout << v1[4];}catch (exception & e) {cout << "Exception :" << e.what() << endl;if (typeid(e) == typeid(RangeException)) {auto r = dynamic_cast<RangeException &>(e);cout << "Vector Dimension :" << r.getDimension() << endl;cout << "Index: " << r.getIndex() << endl;}}return 0;
}

在这里插入图片描述

捕获多种无关异常

不同的异常的捕获

try块中的代码可能会抛出不同类型的异常:
注意,throw出去的是对象,这里我们创建的匿名对象。

class EA: public exception { };
class EB: public exception { };
class C {
public:void foo(int x) {if (x == 1)throw EA();else if (x == 2)throw EB();}
};

而一个catch块只能捕获一种异常:

int main() {C c { };try {c.foo(1);c.foo(2);} catch (EA& a) {cout << a.what() << endl;} catch (EB& b) {cout << b.what() << endl;}

捕获派生异常

派生异常类:

class MyException: public logic_error { };

catch参数类型为基类异常类型,则可以匹配:能捕获基类对象、也能捕获派生类对象

try {throw MyException(); // 抛出派生异常对象
} catch (logic_error& e) {  // catch参数为基类异常,但可以捕获所有派生类异常对象MyException* p = dynamic_cast<MyException*>(&e); // 转指针失败不会再抛异常if (p != nullptr)cout << p->what() << endl;elsecout << e.what() << endl;
}

之前也提到过:dynamic_cast(obj)

  1. 若转型失败且NewType是指针类型,则返回nullptr。
  2. 若转型失败且NewType是引用类型,则抛出std::bad_cast类型的异常

异常处理的次序

捕获异常的正确次序:

派生类的catch块在前、基类的catch块在后

这种写法是错误的:

// (a)
try {...
} catch (logic_error& e) {...
} catch (MyException& e) {...
}

例子:多重捕获异常类

task1:基于Vec3D类、RangeException异常类修改
1.1:将Vec3D的维数抽取出来
1.2:将RangeException改为继承 out_of_range
task2:添加ZeroException,当向量除以一个数为0时抛该异常
该异常应该继承runtime_error
task3:重载operator / () ,为Vec3D类添加标量除法(向量除以一个数)
当除数为0.0时抛异常。
根据IEEE 754 rules:
x > 0.0 : x/0.0 = INF
x < 0.0 : x/0.0 = -INF
0.0 / 0.0 = NaN

Vec3D.h:

#pragma once#include <array>
#include <string>
#include <cmath>
#include <limits>
#include "RangeException.h"
#include "ZeroException.h"
class Vec3D {
public:constexpr static std::size_t DIMENSION = 3;private:std::array <double, DIMENSION> v{1.0,1.0,1.0};bool AreSame(double a, double b) {return std::fabs(a - b) < std::numeric_limits<double>::epsilon();}
public:Vec3D() = default;Vec3D(double x, double y, double z){v[0] = x;v[1] = y;v[2] = z;}double &operator [] (const int index) {if(index >=0 && index < DIMENSION) {return v[index];}else {throw RangeException(DIMENSION,index);}}Vec3D operator /(const double divisor) {//构造当前对象的拷贝Vec3D t(*this);if (AreSame(divisor,0.0))throw ZeroException();for (auto &i : t.v) {i /= divisor;}return t;}};

RangeException.h:

#pragma once#include <iostream>
#include <exception>
class RangeException : public std::out_of_range {
private:std::size_t dimension{ 0 };int index{ 0 };
public:RangeException (std::size_t dimension,const int index) : out_of_range("index exceeds Vector dimension"){this->dimension = dimension;this->index = index;}std::size_t getDimension() {return dimension;}int getIndex() {return index;}
};

ZeroException.h:

#pragma once#include <stdexcept>
#include <exception>class ZeroException : public std::runtime_error {
public:ZeroException() : runtime_error("Divided by 0.0") {};ZeroException(const char* msg) : runtime_error("Divided by 0.0") {};
};

main.cpp:

#include <iostream>
#include <exception>
#include "RangeException.h"
#include "Vec3D.h"
using namespace std;int main()
{Vec3D v1 {1.2,1.3,1.4};try {cout << (v1 / 0.0)[0] << endl;}catch (RangeException & e) {cout << "Exception :" << e.what() << endl;cout << "Vector Dimension :" << e.getDimension() << endl;cout << "Index: " << e.getIndex() << endl;}catch (ZeroException & e) {cout << "Exception :" << e.what() << endl;}return 0;
}

效果:
在这里插入图片描述

catch块的参数类型可以不用引用类型吗?

1、catch () 括号中的异常对象参数可否不用引用类型?
2、catch () 括号中的异常对象参数可否使用指针类型,比如: catch (Exception* e)
3、在多重异常捕获的代码中,若几个catch()括号中的参数是某个类继承链中不同层次的类的对象,此时括号中的参数可否不用引用类型?为什么?

1、catch()括号中的异常对象参数 可以使用:

1对象指针 catch(Exception * o)2对象引用catch(Exception & o)3一个对象catch(Exception  o) < >

2、 在多重异常捕获的代码中,若几个catch()括号中的参数是某个继承链中不同层次的类的对象,此时括号中的参数可以不用引用或指针类型,编译可以通过。

catch()参数如果是对象会发生什么?

1、会发生数据成员丢失

继承链上多态polymophic(虚函数的动态绑定)只有使用对象引用、指针类型才能发生。
当一个子类对象赋值给一个父类对象,这时放生的是对象之间数据成员间的拷贝,如果子类中比父类多出了一些数据成员,多出的数据成员会被截断丢弃,并且通过被赋值后的父类对象调用的虚方法只能是父类本身的虚方法,无法调用子类的虚方法,这是静态绑定。

2、会发生浅拷贝

形参与实参两个异常类对象的对象指针数据成员,指向堆中同一个new出来对象。
发生拷贝构造之后,原来作为catch()函数实参的异常对象要被销毁(先析构再收回空间),异常对象中对象指针指成员指向堆中new出来的对象地址空间被收回。但是作为catch()函数形参的异常对象还在,当形参离开catch块作用域范围前也需要被析构,并其离开catch块时将形参异常对象POP从栈中弹出释放空间。而在形参异常对象被析构时,需要先delete已经在堆中已经回收的对象地址,所以删除一个不存在的地址程序会出错。

3、·作为throw出的异常应该是对象,有可能被catch形参及块内数据覆盖:

在函数作用域范围内,被抛出的异常对象作为本地变量,本地变量存储在栈中,栈存储规律是先入后出,并且地址的大小排列顺序由高向低,而栈帧寻址方式重栈顶(低地址)到栈底(高地址)。

Throw抛异常是逐渐出栈过程,当遇到匹配的catch函数时停止出栈,转入catch作用域中。

也就是说throw出的异常对象高悬在栈顶,但是异常对象的空间已经被收回(对象已经POP出栈)。

但是异常对象存储的数据成员还存在与栈中,由与栈的写入数据序是有高向低地址,被throw抛出的异常对象地址在栈顶最小地址,所以很难被其他数据覆盖。

在catch函数作用域中只要抓取到被throw抛出的异常对象地址,就可以使用这个异常对象中的数据成员。

如果catch函数的形式参数如果是对象,形参对象也要入栈分配空间,这有可能会导致被throw抛出的异常对象数据被覆盖,所以使用对象指针或引用作为catch()函数的形参比较安全,指针或引用占用栈的空间比较小

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377416.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gprs 睡眠模式_GPRS的完整形式是什么?

gprs 睡眠模式GPRS&#xff1a;通用分组无线业务 (GPRS: General Packet Radio Service) GPRS is an abbreviation of General Packet Radio Service. It is a non-voice, high-level speed packet switching technology planned for GSM networks. On 2G and 3G cellular tran…

【C++基础】C++11的noexcept声明符 与 异常传播

目录C noexcept&#xff1a;1、用途2、用法1、noexcept声明符的用法&#xff1a;2、noexcept运算符的用法异常传播1、异常传播的定义2、异常传播中的规则3、异常传播的代价C noexcept&#xff1a; 1、用途 C11使用noexcept指明函数是否抛出异常&#xff1a; 若函数不抛异常&a…

CSS中的文本格式

CSS文字格式 (CSS text formatting) CSS text properties allow you to style your text in various ways very easily. Such as color, alignment, spacing, direction, etc. CSS文本属性使您可以轻松地以各种方式设置文本样式。 例如颜色 &#xff0c; 对齐方式 &#xff0c;…

CSS简写指南

1.margin 1.1 margin:1px 2px 3px(上 左右 下) 1.2 margin:2px 3px(上下 左右) 1.2 margin:1px 3px 2px 3px(上右下左) 2.padding(同上) 3.border border:1px red solid (border-width border-color border-style) 1 2 3border-width&#xff1a;1px 2px 3px; //最多可用四个值…

【C++基础】模板基础与函数模板

目录初识模板函数模板函数模板实例化显式实例化隐式实例化初识模板 求两个int、float、char类型的数据的最大值&#xff1a; C里面要这样写&#xff1a; int maxInt(int x, int y); double maxDouble(double x, double y); char maxChar(char x, char y);C使用函数重载&#…

TAFE的完整形式是什么?

TAFE&#xff1a;拖拉机和农用设备 (TAFE: Tractors and Farm Equipment) TAFE is an abbreviation of Tractors and Farm Equipment Limited. It is an Indian tractor manufacturer which is founded at Chennai in 1960. It is the second-largest tractor manufacturer in …

【C++基础】 类模板

类模板 模板是将类中某些类型变为泛型&#xff0c;从而定义一个模板。 如下&#xff1a; 类模板的语法 直接进行对比&#xff1a; 泛型化之前 泛型化之后类模板的实例化 注意&#xff1a;只要是对类模版进行实例化&#xff0c;编译器就会生成一个类&#xff01;&#xff0…

cocos2d-x游戏开发系列教程-中国象棋02-main函数和欢迎页面

之前两个博客讲述了象棋的规格和工程文件之后&#xff0c;我们继续深入的从代码开始学习cocos2dx首先从程序入口main函数开始main函数int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow) {UNREFERENCED_PARAMETER(h…

【C++基础】模板参数与模板继承

模板参数 默认类型参数 函数参数可以设定一个默认值&#xff0c;我们现在可以对类模板的类型参数设定一个默认类型。 指定泛型Stack的默认类型参数为 int template<typename T int> class Stack{... };当我们这样定义一个对象时&#xff1a; Stack<> stack;使…

【C++基础】STL迭代器

已知&#xff1a; STL组成部分&#xff1a; 容器、迭代器、算法、函数对象、空间分配器 容器&#xff1a;用于保存一组数据&#xff0c;数据个体被称为元素 迭代器&#xff1a;用于遍历容器中的元素&#xff0c;容器都有自己专属的迭代器&#xff0c;只有容器才知道如何遍历自己…

office数据集dslr_DSLR的完整形式是什么?

office数据集dslrDSLR&#xff1a;数码单镜反光 (DSLR: Digital Single-Lens Reflex) DSLR is an abbreviation of digital single-lens reflex. It alludes to a digital camera which with the sensor of digital imaging merges optics and mechanism of single-lens reflex…

envs\TensorFlow2.0\lib\site-packages\tensorflow\python\framework\dtypes.py:516: FutureWarning 解决方案

import tensorflow后的完整报错&#xff1a; D:\Anaconda3\envs\TensorFlow2.0\lib\site-packages\tensorflow\python\framework\dtypes.py:516: FutureWarning: Passing (type, 1) or ‘1type’ as a synonym of type is deprecated; in a future version of numpy, it will b…

生产消费是什么设计模式_快速消费品的完整形式是什么?

生产消费是什么设计模式快消品&#xff1a;快速消费品 (FMCG: Fast-Moving Consumer Goods) FMCG is an abbreviation of Fast-Moving Consumer Goods which are also known as Consumer Packed Goods (CPG). These consumer packed goods allude to the products that are sol…

通过从全局和类内部重载operator new /delete来获取内存管理权

目录1、通过重载获得内存管理权2、容器的内存管理3、重载new、array new、replacement new&#xff0c;接管内存控制权1、重载全局::operator new / ::operator delete以及array版本2、在类里面去重载1、通过重载获得内存管理权 之前的几章学习&#xff0c;是红色的路线。此时…

sml完整形式_教资会的完整形式是什么?

sml完整形式教资会&#xff1a;大学教育资助委员会 (UGC: University Grants Commission) UGC is an abbreviation of the University Grants Commission. It is an organization established by the Indian Union government in agreement with the UGC Act 1956 under the Mi…

erp开发模式_ERP的完整形式是什么?

erp开发模式ERP&#xff1a;企业资源计划 (ERP: Enterprise Resource Planning) ERP is an abbreviation of Enterprise Resource Planning. It is incorporated business management that is executed by a lot of numerous business houses to enhance their productivity an…

关于placement new 和 placement delete的重载,以及basic_string重载new()实例

关于placement new 在https://blog.csdn.net/qq_42604176/article/details/111997397中已经介绍了placement new的形式。 它的形式为new()/delete().我们将分配好内存的指针送入括号中&#xff0c;就完成了初步的调用了。 其实我们可以定义放任何的东西到()内部。只放一个指针…

在eclipse中创建web项目(非myeclipse)

在eclipse中创建web项目(非myeclipse) 在eclipse中如何创建dynamic web project项目 本文的演示是从本地文件创建dynamic web project&#xff0c;从svn检出的同时创建dynamic web project于此类似。我们推荐使用解压版的tomcat6.x版本&#xff0c;来作为服务器。可以到http://…

opengl glut 编译

新建工程glut dll工程&#xff0c;本来想创建lib,工程的&#xff0c;但是想起来&#xff0c;gl是状态机机制。dll方便资源共享等。 添加两个include目录 各种手机电脑平台&#xff0c;网络多媒体开发,mmsplayer&#xff0c;QQ514540005 然后将目录下的lib/glut下面所有的.c文件…

bpo是什么意思_BPO的完整形式是什么?

bpo是什么意思BPO&#xff1a;业务流程外包 (BPO: Business Process Outsourcing) BPO is an abbreviation of Business process outsourcing. It is a convention of a company to another company which is an external provider of services or business operations process…