【问题标题】:C++ throw dereferenced pointerC++ 抛出取消引用的指针
【发布时间】:2011-08-10 16:48:44
【问题描述】:

下面抛出的异常对象的类型是什么:

问题1> range_error r("error"); throw r;

Answer1> range_error 的一个对象

问题2> exception *p = &r; throw *p;

Answer2>异常的切片对象

问题3> exception *p = &r; throw p;

Answer3> 一个指向 range_error 的指针被抛出。捕获处理可以通过动态绑定访问range_error成员函数。

这些问题我答对了吗?

//在VS2010上更新编译运行

#include <iostream>
using namespace std;

class ExClassA
{
public:
    virtual void PrintMe() const
    {
        cout << "ExClassA" << endl;
    }
};

class ExClassB : public ExClassA
{
public:
    virtual void PrintMe() const
    {
        cout << "ExClassB" << endl;
    }
};

int main(int argc, char* argv[])
{   
    ExClassB exClassB;
    ExClassA *p = &exClassB;

    try
    {
        throw *p;
    }
    catch (const ExClassA& e)
    {
        e.PrintMe();        
    }

    try
    {
        throw p;
    }
    catch (const ExClassA* e)
    {
        e->PrintMe();
    }
}

上述程序的第一个try-catch打印“ExClassA”

上述程序的第二次try-catch打印“ExClassB”

【问题讨论】:

  • 参考:李普曼 17.1。
  • 这取决于你说“新”时的想法。
  • @Kerrek,我考虑过“新”可能会引起混淆,因此我已将其从 OP 中删除。

标签: c++


【解决方案1】:

根据对象的静态类型,投掷对象始终会导致所投对象成为您投掷对象的副本。因此,您的前两个答案是正确的。

第三个有点复杂。如果你catch(range_error*) 你不会捕捉到异常,因为类型不匹配。如果您catch(exception*),您将无法在捕获的指针中访问range_error 的成员;不过,您可以 dynamic_cast 将该指针返回到 range_error 指针。

【讨论】:

  • 另外,抛出指向局部变量的指针是不好的。 RAII 将清理该 var,指针将变为 DEAD
  • @Bob,好点没有想到这一点。捕获的异常是通常意义上的局部变量,还是某种特殊的?
  • 这只出现在抛出指针的情况下,你只需要确保指针指向有效的东西。当您按值(不是指针)抛出某些东西时,它会将其复制到任何位置。所以捕获的异常是一个局部变量,它是抛出的异常的副本。
  • @Mark,你能检查一下我的 update2,第二个 try-catch 打印的是我所期望的,但不是你说的。
  • @q0987,您在ClassA 指针上调用虚函数,因此它的工作方式与任何其他多态指针相同。我在回答中的意思是,您无法访问对象中ClassB 的任何非虚拟函数或数据成员。
【解决方案2】:

我认为你在这三个方面都是正确的。被抛出对象的类型 (IIRC) 是被抛出对象的静态类型。我必须深入研究标准一段时间才能找到确切的报价,但一个简单的例子似乎证实了这一点:

struct base {};
struct derived : base {};
void t() {
    derived d;
    base * b = &d;
    throw *b;
}
int main() {
    try {
        t();
    } catch ( derived const & ) {
        std::cout << "derived" << std::endl;
    } catch ( base const & ) {
        std::cout << "base" << std::endl;
    }
}

如果使用了被抛出对象的动态类型,那么*b 将具有derived 类型,第一个catch 会成功,但根据经验执行第二个catch (g++)。

在最后一种情况下,抛出的对象是指向exception 的指针,它引用range_error 对象。细微的差别再次是可以捕获的内容,编译器不会在 catch (range_error*) 块中捕获。答案是正确的,但我会指定指针的类型,就像指针的类型一样。 (指针的类型在某种程度上隐含在答案中)

【讨论】:

  • 相关标准见§15.1/3。
  • @David,结构继承的默认修饰符是PUBLIC是真的吗?
  • @q0987:classstruct 之间的唯一区别是class 的默认访问说明符是private,而struct 是公共的。这适用于继承和成员访问。
【解决方案3】:

三个答案都是正确的。请注意,您必须赶上 第三种情况下的指针类型。

通常抛出异常的方式是:

throw range_error("error");

在 throw 站点上,您通常知道您的异常的确切类型 想扔。关于我能想到的唯一例外是 异常作为参数传入,例如:

void f( std::exception const& whatToDoInCaseOfError )
{
    //  ...
    throw whatToDoInCaseOfError;  //  slices
}

这不是一个常见的情况,但如果你想支持它,你需要一个 您自己的单独异常层次结构,带有虚拟raise 功能:

class MyExceptions
{
public:
    virtual ~MyExceptions() {}
    virtual void raise() const = 0;
};

template<typename ExceptionType>
class ConcreteException : public ExceptionType, public MyExceptions
{
public:
    virtual void raise() const
    {
        throw *this;
    }
};

然后客户端代码将他想要抛出的异常包装在一个 ConcreteException,然后你在上面调用raise 函数,而不是 而不是直接调用throw

【讨论】:

  • 我们可以简单地替换“throw whatToDoInCaseOfError;”中的代码“投掷”以避免切片问题。如果我没记错的话,以这种方式重新抛出不会引入切片问题。
  • @q0987 但是我们不能重新抛出,除非我们在某个地方的 catch 块中,在这种情况下,客户端不必传入异常。
猜你喜欢
  • 2019-05-20
  • 1970-01-01
  • 1970-01-01
  • 2014-10-19
  • 2011-06-17
  • 1970-01-01
  • 2018-05-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多