异常对象的静态类型

Static type of the exception object

我从 C++ Primer(第 5 版,第 18.1.1 节)中阅读了以下内容: "When we throw an expression, the static, compile-time type of that expression determines the type of the exception object." 所以我尝试了以下代码:

#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&,所以我们在第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* 吗?

When we throw an expression, the static, compile-time type of that expression determines the type of the exception object

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

应该动态1分配的对象仍然由那个Base*指针指向。并且没有切片发生在它上面,因为没有尝试复制它。因此,动态调度通过指针访问一个 Derived 对象,该对象将使用覆盖函数。

这 "discrepancy" 就是为什么通常最好在 throw 表达式本身中构造异常对象。


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

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

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

第一个场景

我认为如果您在 类 中添加一些照片,您可以看到更清晰的图片:

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:

First, copy-initializes the exception object from expression

这个复制初始化 切片 对象,就好像你分配了 Base c = b

第二种情况

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

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