避开C++异常处理的陷阱:动态抛出捕捉失效的真相

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/rs_n0prh5...

这里就聊聊动态抛出时,大家经常会感到困惑不解的一个问题,为什么我的动态抛出捕捉不到?

你写的代码可能是这样的:

c 复制代码
class MyExceptionBase { };

class MyExceptionDerived
    : public MyExceptionBase { };

void f(MyExceptionBase& e)
{
    throw e;
}

int main()
{
    MyExceptionDerived e;
    try {
        f(e);
    } catch (const MyExceptionDerived& e) {
        std::cout << "handle MyExceptionDerived"
            << std::endl;
    } catch (...) {
        std::cout << "handle unknow exception"
            << std::endl;
    }
    return 0;
}

一顿操作猛如虎,写的时候满心欢喜,结果编译运行是这样:

复制代码
handle unknow exception

代码本意是希望输出 handle MyExceptionDerived,也就是需要捕捉到 MyExceptionDerived 类型的异常信号,但是异常捕获并没有按照预期地工作。catch (const MyExceptionDerived& e) 这条捕获语句没有被执行,取而代之的是 catch (...) 语句被执行,捕获到的是未知的异常信号。

问题出在哪呢?

在 f() 函数的 throw e; 语句中,抛出的对象的类型是由表达式 e 的静态类型(MyExceptionBase&)决定的,而不是它的动态类型(MyExceptionDerived)。实际抛出的异常类型是 MyExceptionBase,捕获到的异常类型也就是 MyExceptionBase,而不是 MyExceptionDerived

因此,如果你希望抛出的异常能够表现出多态性(即能够根据它的实际类型进行捕获),你需要使用其它方法来抛出异常。

这里提到的静态类型和动态类型,是依据编译器是否能确定的类型来划分。静态类型,是编译时就能确定的类型。动态类型,由程序在运行时决定的,可以在运行时改变。

何以破解?

多态性和虚函数密不可分,抛出异常时不妨从虚函数这块入手,抛出异常的动作改由虚函数实现,修改如下:

arduino 复制代码
class MyExceptionBase
    : public std::exception {
public:
    virtual void raise() {
        throw *this;
    }
};

class MyExceptionDerived
    : public MyExceptionBase {
public:
    virtual void raise() {
        throw *this;
    }
};

void f(MyExceptionBase& e)
{
    // ...
    e.raise();
}

// ...

修改后的代码和之前的代码区别于,在异常信号类定义里添加了虚函数 raise(),用于在 f() 函数内调用并动态抛出异常。

因为 raise() 是虚函数,所以通过对象调用这个方法时,实际调用的实现是基于对象的动态类型(实例化时依据的类型)决定。

对象的实例化类型是 MyExceptionBase,则调用的 raise() 就是 MyExceptionBase 类内的实现;对象的实例化类型是 MyExceptionDerived,则调用的 raise() 就是 MyExceptionDerived 类内的实现。

实际运行结果是:

复制代码
handle MyExceptionDerived

这次的运行结果已经符合预期。

虚函数的使用只能通过对象指针吗?

看着看着,部分读者朋友不禁有个疑问,「为什么以往调用虚函数时都是使用对象指针,而这里的代码却在用引用类型,引用类型也能调用虚函数?」

反过来问,为了体现类的动态特性必须要使用对象指针吗?

这可是妥妥的误区啊。虚函数的实际调用依赖于类的虚函数表,和对象指针没有必然联系。虚函数表的地址保存在对象内部属性中,只要能访问该属性,通过该地址就能调用实际的虚函数实现,也就是决定执行哪个具体的函数。

笔者八戒在之前的文章中也聊过虚函数的原理,感兴趣的同学可关注我查看更多内容。

相关推荐
恋爱绝缘体17 小时前
2020重学C++重构你的C++知识体系
java·开发语言·c++·算法·junit
Z1Jxxx8 小时前
加密算法加密算法
开发语言·c++·算法
乌萨奇也要立志学C++8 小时前
【洛谷】递归初阶 三道经典递归算法题(汉诺塔 / 占卜 DIY/FBI 树)详解
数据结构·c++·算法
️停云️9 小时前
【滑动窗口与双指针】不定长滑动窗口
c++·算法·leetcode·剪枝·哈希
charlie1145141919 小时前
嵌入式现代C++教程: 构造函数优化:初始化列表 vs 成员赋值
开发语言·c++·笔记·学习·嵌入式·现代c++
IT=>小脑虎9 小时前
C++零基础衔接进阶知识点【详解版】
开发语言·c++·学习
在路上看风景10 小时前
01. C++是如何工作的
开发语言·c++
码农小韩10 小时前
基于Linux的C++学习——指针
linux·开发语言·c++·学习·算法
小L~~~10 小时前
绿盟校招C++研发工程师一面复盘
c++·面试
微露清风10 小时前
系统性学习C++-第十九讲-unordered_map 和 unordered_set 的使用
开发语言·c++·学习