【问题标题】:Static type of the exception object异常对象的静态类型
【发布时间】:2017-10-22 07:33:22
【问题描述】:

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

【问题讨论】:

  • 第一种情况,对象被切片。
  • @StoryTeller 谢谢!第二种情况下表达式 b 的静态类型和动态类型是什么? Base* 和 Derived* 分别是?
  • 表达式没有动态类型,只有对象有。
  • 本书试图为新手简化一些非常复杂的语言原理。表达式可以评估为命名对象的内容。对象的静态类型是表达式的类型(有时会调整)。但正式地,只有对象具有动态类型。
  • @StoryTeller 总而言之,每个表达式都有静态类型但没有动态类型,我是否走在正确的轨道上?如果是这样,表达式 b 的静态类型是什么?是 Base* 吗?

标签: c++ try-catch throw static-typing dynamic-binding


【解决方案1】:

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

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

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

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


1 那个指针指向一个本地对象,你在那里做了一个很大的 no no 并得到了一个悬空指针。

【讨论】:

  • 谢谢!!我现在看到了悬空指针。第一种情况呢?我有没有“悬空参考”?
  • @ivy - 不。在第一种情况下,异常对象是Base(在大多数表达式中,T&amp; 类型调整为T)。所以引用的对象是复制的,因此我告诉你它是切片的。
  • 这一切都可以认为是当我们使用 throw 操作时,第一步是从表达式中复制初始化一个临时异常对象吗?也就是说,就好像我在第一种情况下做了Base e = b; e.print(std::cout);(这里b是一个引用)和Base* e = b; e-&gt;print(std::cout);在第二种情况下(这里b是一个指针)。这是正确的思维方式吗? (仍然无法在静态与动态类型之间取得领先……)。
  • @ivy - 你可以认为这是正在发生的事情。只有一个小点,那就是你如何抓住。如果您按值而不是按引用执行此操作,则会将异常对象再次复制到 catch 子句中。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    第一个场景

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

    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

    首先,从表达式复制初始化异常对象

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

    第二种情况

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

    假设你解决了这个问题,然后抛出了一个动态分配的指针,它可以工作,因为你抛出了一个指针,它不会影响对象并保留动态类型信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-13
      • 2018-06-28
      • 2018-04-02
      • 1970-01-01
      • 1970-01-01
      • 2013-06-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多