3

在一次采访中,有人问我为什么按值捕获异常可能是个问题,我回答说这会导致对象切片。这就是我在互联网上找到的,例如这里: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 并且一切正常,没有任何切片。那个怎么样?

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

4

2 回答 2

5

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

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

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


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

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

于 2018-05-18T11:42:24.440 回答
1

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

于 2018-05-18T12:47:10.177 回答