异常处理中对象参数与引用参数的选择
User:
catch()
中的参数如果使用基类的引用,是不是相当于声明了一个引用,它是基类类型的,catch
到 throw
的抛出的对象之后,就相当于这个基类引用指向了抛出的对象?
ChatGPT:
- 是的,你说得很对。当在
catch
块中使用基类的引用作为参数时,相当于声明了一个引用,它是基类类型的。当程序抛出一个异常并且在调用栈中找到匹配的 catch
块时,如果异常的类型与 catch 块中指定的引用类型相匹配,那么这个引用会指向被抛出的异常对象。
这样做的好处是可以使用基类的引用来捕获派生类对象的异常,从而实现多态性。因为派生类对象也是基类对象,所以可以使用基类的引用来引用它们,从而实现了异常处理时的多态性。
User:
- 如果
catch
中的参数不是引用,而是一个对象呢?情况将会是如何的?
ChatGPT:
- 如果在 catch 块中的参数不是引用,而是一个对象,那么将会发生对象的拷贝。这意味着异常对象会被复制一份,而不是通过引用来访问原始的异常对象。
具体来说,当抛出异常并且在调用栈中找到匹配的 catch
块时,如果异常的类型与 catch
块中指定的对象类型相匹配,那么会发生对象的拷贝,即异常对象的副本会被创建并传递给 catch
块。
这种情况下,与使用引用相比,会产生额外的开销,因为需要进行对象的拷贝操作。通常情况下,建议在异常处理中使用引用而不是对象,以避免不必要的对象拷贝。
User:
- 如果是这样,那么整个过程将会产生两次复制构造,一次是
throw
,一次是 catch
,对吗?
ChatGPT:
- 是的,你说得对。如果在
catch
块中的参数是一个对象而不是引用,那么整个过程将会产生两次复制构造。
因此,使用对象作为 catch
块的参数会导致两次复制构造,增加了额外的开销。相比之下,使用引用作为 catch
块的参数可以避免这种额外的开销,因为引用不会引发对象的复制,而是直接引用原始的异常对象。
可以看到 GPT 的回答很好,以下是我写得一个实例,来解释以上的所有信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| #include <iostream> #include <string>
int a = 0;
class Base { int data;
public: Base(int d = 0) : data(d) {} virtual ~Base() { std::cout << "Base destructor called.\n" << std::endl; } virtual void display() const { std::cout << "Base base value:" << data << "\n" << std::endl; } int value() const { return data; } Base(const Base &other) : data(other.data) { a++; std::cout << "Base copy constructor called.\n" << std::endl; } };
class Derived : public Base { char data;
public: Derived(char c = 'a') : Base(), data(c) {} virtual ~Derived() { std::cout << "Derived destructor called.\n" << std::endl; } virtual void display() const override { std::cout << "Derived base value:" << Base::value() << std::endl; std::cout << "Derived derived value:" << data << "\n" << std::endl; } Derived(const Derived &other) : Base(other), data(other.data) { a++; std::cout << "Derived copy constructor called.\n" << std::endl; } };
void throwException(bool useDerived) { if (useDerived) { Derived d('b'); throw d; } else { Base b(100); throw b; } }
int main() { try {
try { std::cout << "---------------开始构造--------------" << std::endl; throwException(true); } catch (Base &b) { std::cout << "\na: " << a << std::endl; b.display(); std::cout << "---------------开始析构--------------" << std::endl; }
try { std::cout << "---------------开始构造--------------" << std::endl; throwException(true);
} catch (Base b) { std::cout << "\na: " << a << std::endl; b.display(); std::cout << "---------------开始析构--------------" << std::endl; }
try { std::cout << "---------------开始构造--------------" << std::endl; throwException(false); } catch (Base &b) { std::cout << "\na: " << a << std::endl; b.display(); std::cout << "---------------开始析构--------------" << std::endl; }
try { std::cout << "---------------开始构造--------------" << std::endl; throwException(false); } catch (Base b) { std::cout << "\na: " << a << std::endl; b.display(); std::cout << "---------------开始析构--------------" << std::endl; }
} catch (const std::exception &e) { std::cerr << "\nCaught exception: " << e.what() << std::endl; }
return 0; }
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| ./main ---------------开始构造-------------- Base copy constructor called.
Derived copy constructor called.
Derived destructor called.
Base destructor called.
a: 2 Derived base value:0 Derived derived value:b
---------------开始析构-------------- Derived destructor called.
Base destructor called.
---------------开始构造-------------- Base copy constructor called.
Derived copy constructor called.
Derived destructor called.
Base destructor called.
Base copy constructor called.
a: 5 Base base value:0
---------------开始析构-------------- Base destructor called.
Derived destructor called.
Base destructor called.
---------------开始构造-------------- Base copy constructor called.
Base destructor called.
a: 6 Base base value:100
---------------开始析构-------------- Base destructor called.
---------------开始构造-------------- Base copy constructor called.
Base destructor called.
Base copy constructor called.
a: 8 Base base value:100
---------------开始析构-------------- Base destructor called.
Base destructor called.
|
可以看到catch()
的参数尽管可以被设为引用,但是其实仍然要发生异常对象的复制(用复制构造函数)。如果没有被设置为引用,那么在最后,不仅要析构异常对象副本,还要析构异常对象本身。