我从C ++ Primer(第5版,第18.1.1节)中读到以下内容: "当我们抛出一个表达式时,该表达式的静态编译时类型决定了异常对象的类型。"所以我尝试了以下代码:
#include <iostream>
class Base{
public:
virtual void print(std::ostream& os){os << "Base\n";}
};
class Derived: public Base{
public:
void print(std::ostream& os){os << "Derived\n";}
};
int main(){
try{
Derived d;
Base &b = d;
b.print(std::cout); //line 1
throw b;
}
catch(Base& c){
c.print(std::cout); //line 2
}
return 0;
}
给出了以下输出:
Derived
Base
我想我理解为什么会出现这样的输出:在第1行,我们有动态绑定。现在当我们抛出b时,它基于b的静态类型,这意味着c的静态类型和动态类型都是Base&amp;,因此我们在第2行看到结果。
但是,如果我使用指针而不是引用:
int main(){
try{
Derived d;
Base *b = &d;
b->print(std::cout); //line 1
throw b;
}
catch(Base* c){
c->print(std::cout); //line 2
}
return 0;
}
现在输出变为:
Derived
Derived
这似乎暗示c的静态类型是Base *,但c的动态类型是Derived *,为什么?不应该是静态和动态类型的c都是Base *?
答案 0 :(得分:2)
当我们抛出一个表达式时,该表达式的静态编译时类型决定了异常对象的类型
以上是完全正确的。你忘了,指针也是对象。当你抛出指针时,那是你的异常对象。
应该动态 1 分配的对象仍然由Base*
指针指向。并且没有切片发生,因为没有尝试复制它。因此,动态调度via-pointer访问Derived
对象,该对象将使用覆盖函数。
这种“差异”是为什么通常最好在throw表达式本身中构造异常对象。
<子> 1 那个指针指向一个本地对象,你做了一个很大的不,没有那里,并给自己一个悬垂的指针。 子>
答案 1 :(得分:2)
在第一种情况下,您正在抛出一个新的Base
类实例来调用复制构造函数,因为您将对Base
的引用传递给throw
运算符。
在第二种情况下,您抛出指向类型为Derived
的堆栈分配对象的指针,该对象在抛出异常时超出范围,因此您捕获然后取消引用导致未定义行为的悬空指针。
答案 2 :(得分:0)
第一种情况
我认为如果你在课程中添加一些印刷品,你会看到更清晰的画面:
struct Base {
Base() { std::cout << "Base c'tor\n"; }
Base(const Base &) { std::cout << "Base copy c'tor\n"; }
virtual void print(std::ostream& os) { std::cout << "Base print\n"; }
};
struct Derived: public Base {
Derived() { std::cout << "Derived c'tor\n"; }
Derived(const Derived &) { std::cout << "Derived copy c'tor\n"; }
virtual void print(std::ostream& os) { std::cout << "Derived print\n"; }
};
输出是:
Base c'tor
Derived c'tor
Derived print
throwing // Printed right before `throw b;` in main()
Base copy c'tor
Base print
正如您所看到的,在调用throw b;
时,存在异常的不同临时Base
对象的副本构造。来自cppreference.com:
首先,从表达式
复制初始化异常对象
此复制初始化切片对象,就像您分配了Base c = b
第二种情况
首先,您正在抛出一个指向本地对象的指针,导致未定义的行为,请务必避免这种情况!
假设您修复了这个问题,并且您抛出一个动态分配的指针,它会起作用,因为您抛出的指针不会影响对象并保留动态类型信息。