C++异常处理 -- 异常对象(Exception Object)

本文详细探讨了C++中异常处理的基本概念,包括如何使用throw抛出不同类型的异常,以及如何利用catch捕获这些异常。文章通过实例展示了值传递与引用传递的区别,并解释了在不同情况下如何高效地处理异常。

在C++异常处理中,throw可以抛出任何对象,可以是int类型,也可以是class类型,对于catch块而言,有两种选择:

1)catch这个类型,例:

int main()
{
    int a=1;
    try
    {
        throw a;
    }
    catch(int) // 捕获int型异常,无法获知a的内容
    {
        cout<<"Something wrong happened!"<<endl;
    }
    
    return 0;
}

2)catch这个类型的对象,例:

int main()
{
    int a=1;
    try
    {
        throw a;
    }
    catch(int b) // 捕获int型异常,可以获知a的值
    {
        cout<<"Something wrong happened: "<<b<<endl;
    }
    
    return 0;
}

这两者的区别很明显,后者可以通过访问异常对象(本例中是b)从而获取异常信息。


throw和catch异常对象很像函数调用和函数定义,因此类似地,也可以catch一个对象的引用,这样可以减少拷贝一个复杂对象的开销,例如:

int main()
{
    int a=1;
    try
    {
        throw a;
    }
    catch(int &b) // 捕获int型异常,减少开销
    {
        cout<<"Something wrong happened: "<<b<<endl;
    }
    
    return 0;
}


那么,很自然的想到一个问题:

如果catch的是引用,那么修改该引用,原对象是否会被同步修改?

答案是“否”,下面我们通过一个例子说明在throw…catch这个过程中发生了什么:

首先定义一个异常类用于throw和catch:

class CExceptionTest
{
public:
    CExceptionTest(int a=0)
    {
        cout<<"Default Constructor:"<<a<<endl;
        m_a = a;
    }
    CExceptionTest(CExceptionTest &another)
    {
        m_a = another.m_a;
        cout<<"Copy Constructor:"<<m_a<<endl;
    }
    CExceptionTest& operator=(int a)
    {
        m_a=a;
        cout<<"Assign Constructor:"<<m_a<<endl;
        return *this;
    }
    int Get()
    {
        return m_a;
    }
    void Set(int a)
    {
        m_a = a;
    }

private:
    int m_a;
};


然后我们测试下如果是pass-by-value传值方式catch异常的时候,会“暗地里”做哪些事情?

CExceptionTest g_a(1); // 默认构造函数
void f1()
{
    g_a=2;             // 调用赋值构造函数
    throw g_a;         // 调用拷贝构造函数生成临时对象(tempObj)
}

void f2()
{
    try
    {
        f1();
    }
    catch(CExceptionTest b) // 调用拷贝构造函数
    {
        cout<<"Exception happened:"<<b.Get()<<endl;
        b.Set(3);
        cout<<"Exception happened:"<<b.Get()<<endl;
        cout<<"g_a.Get() is "<<g_a.Get()<<endl;      
    }
}
int main()
{
    f2();
    return 0;
}


编译并执行后,输出为:

1:Default Constructor:1
2:Assign Constructor:2
3:Copy Constructor:2
4:Copy Constructor:2
5:Exception happened:2
6:Exception happened:3
7:g_a.Get() is 2

从结果中我们可以看到:

在throw g_a时,会生成一个临时对象,假设为tempObj,此时调用拷贝构造函数,即输出中的第3行,然后catch时,又将该临时对象通过拷贝构造函数生成b,输出中第4行反映了这一点,此时即使我们修改了b的信息,也不会影响到被抛出的对象g_a,这是很正常的。


那么如果我们使用pass-by-ref传引用的方式呢?

我们将f2函数中catch处代码修改为引用后,f2函数形如:

void f2()
{
    try
    {
        f1();
    }
    catch(CExceptionTest &b) // 引用该临时对象tempObj
    {
        cout<<"Exception happened:"<<b.Get()<<endl;
        b.Set(3);
        cout<<"Exception happened:"<<b.Get()<<endl;
        cout<<"g_a.Get() is "<<g_a.Get()<<endl;
    }
}


此时我们编译并执行,输出变为:

1:Default Constructor:1
2:Assign Constructor:2
3:Copy Constructor:2
4:Exception happened:2
5:Exception happened:3
6:g_a.Get() is 2


有两点变化:

第一、拷贝函数的调用减少了一次,这说明b对象直接引用了临时对象;

第二、修改b也不会影响被抛出的异常对象g_a,说明throw和catch无论是传值还是传引用都不会影响被抛出的对象。



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值