【问题标题】:Exception Handling + polymorphism, doesnt work if the exception method, doesnt work in the same class异常处理+多态,如果异常方法不起作用,在同一个类中不起作用
【发布时间】:2012-02-23 00:46:20
【问题描述】:

我正在尝试这样的代码

//A.hpp
 class A{
   public:
       A() {} 
       virtual const char *message() const {return "A ERROR";}
 };

 //B.hpp

 #include "A.hpp"

 class B:public A {
   public:
       B() {}
       const char *message() const {return "B ERROR";}
 };

//main.cpp
 #include "A.hpp"
 #include "B.hpp"

void foo(const A& a) {
   /* case 1 */ throw a;   /* (or) */ /* case 2 */ throw B(); // LINE 100
}

int main() {

  B b;
  A &a(b);
  b.message(); // OUTPUT: B ERROR

  try {
      foo(a);
  } catch (const A& a) {
     std::cout<<"EXCEPTION CALLED "<<a.message()<<std::endl;
  }
  return 0;
}

在这种情况下,如果我使用 情况1: 扔一个; //a 是 B b 的引用; 输出:一个错误

案例 2: 抛出 B(); // 创建新的 B; 输出:B 错误

我不明白的是,为什么在两个情况下没有一致性,

如果你通过引用传递,所有的时间,应该有一些一致性, 如果我在函数内部创建一个新变量,在 try 块中调用,那么它会调用正确的虚拟方法,否则它不会......谁能告诉我控制流程......请指教。 ...

【问题讨论】:

    标签: c++ exception polymorphism virtual-functions throw


    【解决方案1】:

    因为在抛出之前复制了一个对象。

    即使foo 的参数a 在运行时指向B 的实例,重要的是throw 表达式的compile-time 类型。因此,实际上,B 的实例被传递给A 的复制构造函数(这是合法的,因为B 继承了A)并创建了一个新的A 实例然后抛出。

    复制的原因是编译器必须保证异常对象的生命周期,只要有任何catch 块可以捕获它。因此,它不会冒堆栈对象“从堆栈边缘脱落”或堆对象被堆栈展开期间调用的某个析构函数释放的风险。

    【讨论】:

      【解决方案2】:

      为了完整起见,我觉得有必要指出,就像虚拟副本(也称为克隆)解决了通过基础进行复制一样,虚拟(重新)抛出解决了通过基础进行的复制:

      struct base {
          virtual void
          rethrow() const
          { throw *this; }
      
          // it's also usual to make the base type abstract
          // so that users can't slice, e.g. boost::exception
          // it's also possible to make copying protected
      };
      
      struct derived: base {
          void
          rethrow() const override
          { throw *this; }
      };
      
      void
      foo(base const& b)
      {
          // no: slices
          // throw b;
      
          b.rethrow(); // Ok
      }
      

      【讨论】:

        【解决方案3】:

        Throwing 复制变量。您在 foo 中的 throw a 实际上并没有从 main 中抛出对 a 的引用,它实际上抛出了 a 的副本。在 catch 语句中,您通过引用来捕获该副本。由于在 foo 中,a 是对 A 的引用,因此副本对对象进行切片,它变成了 A,因此失去了它曾经是 B 的事实。

        【讨论】:

          【解决方案4】:

          我无法准确理解您的要求,但是... 首先,不要将 catch 的变量命名为与局部变量相同的名称,即您使用“a”两次来表示不同的事物。您可能会认为您通过引用捕获的项目是您传递给 foo 的 a,如果 foo() 抛出 B,这将不是真的。为了清楚起见,将您的捕获更改为

          catch (const A& ex)
          {
           ...ex.message()...
          }
          

          我怀疑发生的事情是,当在 try 之外声明时,a 仍在范围内,并且 a.message() 调用了局部变量 a。如果在 try 中声明,则 a 不再在 catch 范围内,因此您通过引用捕获的 a 会调用其消息。更改 catch 的变量名应该可以消除看似模棱两可的行为。

          【讨论】:

          • 无论在那里使用a,都不应该显示观察到的消息。
          • 我无法从问题中确切地看出在什么条件下抛出了什么。我刚刚在最后一句中注意到,如果在 try 内或 try 外声明 a 会有不同的行为。由于 没有理由 像这样重用变量名“a”,因此清理它似乎对我来说是明智的。不知道为什么有人认为这是一个坏主意。
          • 投反对票不是因为你的建议不好,(这是个好建议)而是因为这不是问题的答案。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-02-06
          • 1970-01-01
          • 1970-01-01
          • 2017-04-05
          • 1970-01-01
          • 2019-05-07
          • 2016-03-18
          相关资源
          最近更新 更多