异常对象的静态类型

Static type of the exception object

本文关键字:静态类 类型 静态 对象 异常      更新时间:2023-10-16

我从C++入门(第 5 版,第 18.1.1 节)中阅读了以下内容:"当我们抛出一个表达式时,该表达式的静态编译时类型决定了异常对象的类型。所以我尝试了以下代码:

#include <iostream>
class Base{
  public:
  virtual void print(std::ostream& os){os << "Basen";}
};
class Derived: public Base{
  public:
  void print(std::ostream& os){os << "Derivedn";}
};
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&,因此我们在第 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的动态类型是派生*,为什么?c的静态和动态类型不应该都是Base*吗?

当我们抛出一个表达式时,该表达式的静态编译时类型决定了异常对象的类型

以上是完全正确的。你忘记了,指针也是对象。当你抛出一个指针时,这就是你的异常对象

您应该动态分配1 的对象仍由该Base*指针指向。并且不会对其进行切片,因为没有尝试复制它。因此,通过指针进行动态调度访问Derived对象,该对象将使用重写函数。

这种"差异"就是为什么通常最好在抛出表达式本身中构造异常对象的原因。


1 那个指针指向一个本地对象,你在那里做了一个很大的不,并给自己一个悬空的指针。

在第一种情况下,您正在抛出Base类调用复制构造函数的新实例,因为您将对Base的引用传递到运算符throw

在第二种情况下,您将抛出一个指向类型为 Derived 的堆栈分配对象的指针,该对象在引发异常时超出范围,因此您捕获然后取消引用导致未定义行为的悬空指针。

第一个场景

我认为,如果你在课堂上添加一些印刷品,你可以看到更清晰的画面:

struct Base {
    Base() { std::cout << "Base c'torn"; }
    Base(const Base &) { std::cout << "Base copy c'torn"; }
    virtual void print(std::ostream& os) { std::cout << "Base printn"; }
};
struct Derived: public Base {
    Derived() { std::cout << "Derived c'torn"; }
    Derived(const Derived &) { std::cout << "Derived copy c'torn"; }
    virtual void print(std::ostream& os) { std::cout << "Derived printn"; }
};

输出为:

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

第二种情况

首先,您正在抛出指向本地对象的指针,导致未定义的行为,请一定避免这种情况!

假设您修复了这个问题,并且抛出了一个动态分配的指针,它就可以工作,因为您抛出了一个指针,这不会影响对象并保留动态类型信息。