链接异常的实现是如何工作的?

发布于 2024-09-16 09:28:19 字数 3819 浏览 5 评论 0原文

我之前问过一个关于如何在 C++ 中链接异常的问题,其中之一这些答案为如何做到这一点提供了一个绝妙的解决方案。问题是我看不懂代码,试图在评论中进行这种讨论实在是太麻烦了。所以我认为最好完全开始一个新问题。

代码包含在下面,我已经清楚地标记了我不明白的每个部分。代码下面包含了我不明白的内容的描述。该代码由 Potatoswatter 编写。


代码


struct exception_data { // abstract base class; may contain anything
    virtual ~exception_data() {}
};

struct chained_exception : std::exception {
    chained_exception( std::string const &s, exception_data *d = NULL )
        : data(d), descr(s) {
        try {
            link = new chained_exception;

            // ----------------------------------------------------------------
            //   How does this work (section 1)?
            throw;
            // ----------------------------------------------------------------

        } catch ( chained_exception &prev ) {
            // ----------------------------------------------------------------
            //   How does this work (section 2)?
            swap( *link, prev );
            // ----------------------------------------------------------------
        } // catch std::bad_alloc somehow...
    }

    friend void swap( chained_exception &lhs, chained_exception &rhs ) {
        std::swap( lhs.link, rhs.link );
        std::swap( lhs.data, rhs.data );
        swap( lhs.descr, rhs.descr );
    }

    virtual char const *what() const throw() { return descr.c_str(); }

    virtual ~chained_exception() throw() {
        // --------------------------------------------------------------------
        //   How does this work (section 3)?
        if ( link && link->link ) delete link; // do not delete terminator
        // --------------------------------------------------------------------
        delete data;
    }

    chained_exception *link; // always on heap
    exception_data *data; // always on heap
    std::string descr; // keeps data on heap

private:
    chained_exception() : link(), data() {}
    friend int main();
};

void f() {
    try {
        throw chained_exception( "humbug!" );
    } catch ( std::exception & ) {
        try {
            throw chained_exception( "bah" );
        } catch ( chained_exception &e ) {
            chained_exception *ep = &e;
            for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
                // Print ep->what() to std::cerr
            }
        }
    }

    try {
        throw chained_exception( "meh!" );
    } catch ( chained_exception &e ) {
        for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
            // Print ep->what() to std::cerr
        }
    }
}

int main() try {
    // ------------------------------------------------------------------------
    //   How does this work (section 4)?
    throw chained_exception(); // create dummy end-of-chain
    // ------------------------------------------------------------------------
} catch( chained_exception & ) {
    // body of main goes here
    f();
}

运行代码给出以下输出:

bah
humbug!
meh!

throw; inside try-block:我不明白的内容

  1. :我从来没有以前见过这个。我认为 throw; 唯一有效的地方是在 catch 块内,以重新抛出捕获的内容。那么这有什么作用呢?一些调试显然表明抛出的异常是之前抛出的异常,但那是在完全不同的 try 块内。事实上,它甚至位于 struct 声明之外!

  2. 交换字段:为什么我们需要交换异常字段?仅仅复制指针还不够吗?这是为了防止字段指向的结构被过早地从堆中删除吗?

  3. 检查linklink的链接:我可以理解检查link 不是 NULL (即使删除 NULL 指针也没有效果),但是为什么需要检查 link 的链接呢?

  4. 抛出虚拟异常:为什么需要这个虚拟异常?它被抛出,但随后掉落。为什么我们需要这个作为链条的末端?

I previously asked a question about how to chaining exceptions in C++, and one of the answers provided a nifty solution to how it can be done. The problem is that I don't understand the code, and trying to have this kind of discussion in the comments is just too much of a bother. So I figured it's better to start a new question entirely.

The code is included below and I've clearly marked each section which I don't get. A description of what I don't understand is included below the code. The code was written by Potatoswatter.


Code


struct exception_data { // abstract base class; may contain anything
    virtual ~exception_data() {}
};

struct chained_exception : std::exception {
    chained_exception( std::string const &s, exception_data *d = NULL )
        : data(d), descr(s) {
        try {
            link = new chained_exception;

            // ----------------------------------------------------------------
            //   How does this work (section 1)?
            throw;
            // ----------------------------------------------------------------

        } catch ( chained_exception &prev ) {
            // ----------------------------------------------------------------
            //   How does this work (section 2)?
            swap( *link, prev );
            // ----------------------------------------------------------------
        } // catch std::bad_alloc somehow...
    }

    friend void swap( chained_exception &lhs, chained_exception &rhs ) {
        std::swap( lhs.link, rhs.link );
        std::swap( lhs.data, rhs.data );
        swap( lhs.descr, rhs.descr );
    }

    virtual char const *what() const throw() { return descr.c_str(); }

    virtual ~chained_exception() throw() {
        // --------------------------------------------------------------------
        //   How does this work (section 3)?
        if ( link && link->link ) delete link; // do not delete terminator
        // --------------------------------------------------------------------
        delete data;
    }

    chained_exception *link; // always on heap
    exception_data *data; // always on heap
    std::string descr; // keeps data on heap

private:
    chained_exception() : link(), data() {}
    friend int main();
};

void f() {
    try {
        throw chained_exception( "humbug!" );
    } catch ( std::exception & ) {
        try {
            throw chained_exception( "bah" );
        } catch ( chained_exception &e ) {
            chained_exception *ep = &e;
            for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
                // Print ep->what() to std::cerr
            }
        }
    }

    try {
        throw chained_exception( "meh!" );
    } catch ( chained_exception &e ) {
        for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
            // Print ep->what() to std::cerr
        }
    }
}

int main() try {
    // ------------------------------------------------------------------------
    //   How does this work (section 4)?
    throw chained_exception(); // create dummy end-of-chain
    // ------------------------------------------------------------------------
} catch( chained_exception & ) {
    // body of main goes here
    f();
}

Running the code gives the following output:

bah
humbug!
meh!

What I don't understand

  1. throw; inside try-block: I've never seen this before. The only place I've thought throw; to be valid was inside a catch-block to rethrow what was caught. So what does this do? Some debugging apparently shows that the thrown exception is what was thrown previously, but that was inside a completely different try-block. In fact, it was even outside the struct declaration!

  2. Swap fields: Why do we need to swap the exception fields? Wouldn't just copying of the pointers be enough? Is this to prevent the structures to which the fields point at from being deleted from the heap prematurely?

  3. Check link and link's link: I can understand checking that link is not NULL (even though deleting a NULL pointer has no effect), but why the need to check the link's link?

  4. Throw dummy exception: Why is this dummy needed? It's thrown but then dropped. Why do we need this as an end to the chain?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

南冥有猫 2024-09-23 09:28:19

聪明的代码——对potatoswatter 的这一代码表示敬意。我认为我必须找到解决最后一项的方法。

  1. throw; 重新抛出活动异常。仅当 catch 块位于堆栈上时它才有效。我不记得我是在哪里看到这个花絮的,但它可能是在其他一些问题的背景下出现的。裸抛出让我们可以通过在chained_exception构造函数中捕获当前异常来访问它。换句话说,构造函数中的 prev 是对我们当前正在处理的异常的引用。

  2. 你是对的。这可以防止重复删除。

  3. 哨兵异常,即在 main 中抛出的异常,永远不应该被删除。此异常的一个标识属性是它的 link 成员为 NULL

  4. 这是我不喜欢的部分,但想不出一个简单的方法。只有当 catch 块处于活动状态时,才能调用唯一可见的 chained_exception 构造函数。 IIRC,没有活动 catch 块的裸露抛出是禁忌。因此,解决方法是放入 main 并将所有代码放入 catch 块中。

现在,如果您在多线程代码中尝试此方法,请确保您很好地理解 (4)。您必须在线程入口点中复制此内容。

Clever code - kudos to potatoswatter on this one. I think that I would have to find some way around the last item though.

  1. throw; rethrows the active exception. It is only valid if a catch block is on the stack. I can't recall where I came across that tidbit at but it was probably on SO in the context of some other question. The bare throw gives us access to the current exception by catching it in the chained_exception constructor. In other words, prev in the constructor is a reference to the exception that we are currently processing.

  2. You are correct here. This prevents double deletion.

  3. The sentinel exception, the one thrown in main, should never be deleted. The one identifying attribute of this exception is that it's link member is NULL.

  4. This is the part that I don't like but cannot think of an easy way around. The only visible chained_exception constructor can only be called when a catch block is active. IIRC, a bare throw without an active catch block is a no-no. So, the workaround is to throw in main and put all of your code in the catch block.

Now, if you try this method in multi-threaded code, make sure that you understand (4) very well. You will have to replicate this in your thread entry point.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文