【问题标题】:C++: object slicing and exceptionsC++:对象切片和异常
【发布时间】:2018-10-28 20:19:03
【问题描述】:

在一次采访中,有人问我为什么按值捕获异常可能是个问题,我回答说这会导致对象切片。这是我在互联网上找到的,例如这里:https://www.viva64.com/en/w/v746/

但是现在我正在尝试进行实验,但在按值捕获时找不到切片示例。切片的通常场景(没有例外)是这样的:

Derived d1;
Derived d2;
Base& b1 = d1;
Base& b2 = d2;
b1 = b2;

在最后一行调用 Base 的赋值运算符,它只复制 Derived 对象的 Base 部分。因此,b1 的基于部分是从 d2 复制的,而 b1 的派生部分仍然来自 d2。不好。

但是当按值捕获异常时怎么会发生这种情况呢?

我尝试了这段代码(同时使用:g++ 和 Sun CC 编译器):

struct Base
{
    virtual void print() const
    {
        cout << "{ Base: " << m << " }" << endl;
    }

    Base(int _m = 0) : m(_m) {}

    int m;
};

struct Derived : Base
{
    Derived(int _m = 0, int _n = 0) : Base(_m), n(_n) {}

    void print() const
    {
        cout << "{ Base: " << m << ", Derived: " << n << " }" << endl;
    }

    int n;
};

int main()
{
    try
    {
        try
        {
            throw Derived(3, 300);
        }
        catch(Base x)
        {
            cout << "Inner catch: ";
            x.print();
            throw;
        }
    }
    catch(Derived y)
    {
        cout << "Outer catch: ";
        y.print();
    }    
}

输出是:

Inner catch: { Base: 3 }
Outer catch: { Base: 3, Derived: 300 }

所以我抛出 Derived 异常,捕获它的 Base BY VALUE 并重新抛出,然后捕获 Derived BY VALUE 并且一切正常,没有任何切片。 怎么样?

有人可以提供一个在按值捕获时切片的示例吗?

【问题讨论】:

  • 切片更像f(Base b){},然后是f(derived);
  • 您是否尝试将内部捕获中的throw 更改为throw x?我想它可能会产生影响

标签: c++ exception exception-handling object-slicing rethrow


【解决方案1】:

即使catch(Base) 对抛出的Derived 对象进行切片,rethrow 使用原始异常对象而不是切片副本。

来自http://en.cppreference.com/w/cpp/language/throw

重新抛出当前处理的异常。放弃当前catch块的执行并将控制权传递给下一个匹配的异常处理程序(但不是在同一个try块之后的另一个catch子句:它的复合语句被认为已经“退出”),重用现有的异常对象:不生成新对象。仅当当前正在处理异常时才允许使用此形式(如果以其他方式使用,则调用 std::terminate)。如果在构造函数上使用,与 function-try-block 关联的 catch 子句必须通过重新抛出退出。


请注意,如果将throw; 替换为throw x;,则会抛出Base 实例并且不会被捕获,从而导致std::abort() 被调用。事实上,切片的Derived 不能被取消切片(这就是为什么我们通常不喜欢切片,除非它们是 pizza 切片)被 catch (Derived) 捕获。

作为结论,我会坚持“按值抛出,按(const)引用捕获)”。在这个特定的示例中,您可以通过切片值进行捕捉,但一般情况并非如此。 Serge Ballesta's answer 提供了一个按值捕获导致麻烦的示例。在您最喜欢的搜索引擎上快速搜索可以帮助您找到其他按价值捕获正在寻找麻烦的情况。

【讨论】:

  • 好的,所以关于抛出和捕获异常的建议:'按值抛出,按引用捕获'实际上可以替换为'按值抛出,按你想要的捕获,用纯抛出重新抛出没有论据”。 (如果您想修改异常对象,请通过引用捕获)。
  • @AndreyRubliov 如果这个答案适合您,您可以通过检查其分数下的标记来接受它。如果它不适合您,您可以提出问题/提出改进建议。
【解决方案2】:

按值捕获异常的另一个问题是,它需要异常的完整副本。如果您接近 StackOverflow 条件(或已经在处理一个),您可能处于无法复制且无法执行 catch 子句的用例中。

【讨论】:

  • 对。按值捕获的另一个问题是异常类的复制构造函数是否可以抛出异常(并且它可以在内存不足的情况下抛出异常)。然后 std::terminate() 将被调用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-24
  • 1970-01-01
  • 1970-01-01
  • 2019-07-29
  • 2018-02-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多