异常应该在 C++ 中链接吗?

发布于 2024-09-15 12:07:19 字数 529 浏览 5 评论 0原文

我刚刚完成了一个 C++ 程序的工作,在该程序中我实现了自己的异常(尽管源自 std::exception)。当一个异常导致连锁反应,向上传播错误并引发其他异常时,我所采用的做法是在模块中的每个适当步骤(读取类)连接错误消息。即旧的异常本身被删除并创建一个新的异常,但带有更长的错误消息。

这可能对我的小程序有用,但最终我对我的方法不是很满意。首先,除了最后一个例外之外,不保留行号(尽管目前尚未应用)和文件名;实际上,第一个例外中的信息最令人感兴趣。

我认为通过将异常链接在一起可以更好地处理这个问题;即旧异常在新异常的构造函数中提供。但如何实施呢?当异常超出方法的范围时,异常是否会消失,从而阻止人们使用异常指针?如果异常可以是任何派生类的,如何复制和存储异常?

这最终让我考虑 C++ 中的链接异常是否是一个好主意。也许人们应该只创建一个异常,然后向其中添加额外的数据(就像我一直在做的那样,但可能以一种更好的方式)?

您对此有何回应?由另一个引起的异常是否应该链接在一起以保留某种“异常跟踪”——以及应该如何实现? ——或者应该使用单个例外并附加附加数据——应该如何完成?

I just finished work on a C++-program where I've implemented my own exceptions (although derived from std::exception). The practice I've applied when one exception causes a chain reaction, propagating the error upwards and giving rise to other exceptions, is to concatenate the error message at each appropriate step across the modules (read classes). I.e. the old exception itself is dropped and a new exception is created, but with a longer error message.

This may have worked for my small program, but I wasn't very satisfied with my approach in the end. For one, line numbers (although not applied at the moment) and file names are not retained except for the last exception; and really that information is of most interest in the first exception.

I figure this could have been handled better by chaining exceptions together; i.e. the old exception is provided in the constructor of the new exception. But how would that be implemented? Does not exceptions die when they go out of scope from the method, thereby preventing one to use exception pointers? And how to copy and store the exception if the exception can be of any derived class?

This ultimately lead me to consider whether chaining exceptions in C++ is such a good idea after all. Perhaps one should just create one exception and then add additional data to that (like I've been doing, but probably in a much better manner)?

What is your response to this? Should exceptions caused by another be chained together to retain a sort of "exception trace" -- and how should that be implemented? -- or should a single exception be used and additional data attached to it -- and how should that be done?

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

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

发布评论

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

评论(4

愛放△進行李 2024-09-22 12:07:19

自从提出这个问题以来,C++11 标准已经发生了显着的变化。
我在有关异常的讨论中不断错过这一点,但以下方法(嵌套异常)可以解决问题:

使用 std::nested_exceptionstd::throw_with_nested

StackOverflow 此处此处,介绍如何在代码中获取异常的回溯,而无需调试器或繁琐的日志记录,只需编写一个适当的异常处理程序将重新抛出嵌套异常。

由于您可以使用任何派生异常类来执行此操作,因此您可以向此类回溯添加大量信息!
您还可以查看我的 GitHub 上的 MWE,其中回溯看起来类似于这:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Since this question has been asked noteable changes have been made to the standard with C++11.
I am continually missing this in discussions about exceptions, but the following approach, nesting exceptions, does the trick:

Use std::nested_exception and std::throw_with_nested

It is described on StackOverflow here and here, how you can get a backtrace on your exceptions inside your code without need for a debugger or cumbersome logging, by simply writing a proper exception handler which will rethrow nested exceptions.

Since you can do this with any derived exception class, you can add a lot of information to such a backtrace!
You may also take a look at my MWE on GitHub, where a backtrace would look something like this:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
假情假意假温柔 2024-09-22 12:07:19

如果您希望数据比接收它的 catch 块更长寿,除了通过 throw; 重新抛出之外,有必要将数据从异常对象复制到链中。 (例如,其中包括如果 catch 块通过 throw obj; 退出。)

这可以通过将要保存在堆上的数据并实现 <例如,对异常内的私有数据进行 code>swap(C++0x 中的move)。

当然,在使用带有异常的堆时需要小心……但话又说回来,在大多数现代操作系统中,内存过量使用完全阻止了 new 抛出,无论好坏。良好的内存裕度和在完全崩溃时从链中删除异常应该可以保证它的安全。

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;
            throw;
        } catch ( chained_exception &prev ) {
            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() {
        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 ) {
                std::cerr << ep->what() << std::endl;
            }
        }
    }

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

int main() try {
    throw chained_exception(); // create dummy end-of-chain
} catch( chained_exception & ) {
    // body of main goes here
    f();
}

输出(适当地脾气暴躁):

bah
humbug!
meh!

It is necessary to copy the data out of an exception object, into a chain, if you want it to outlive the catch block that receives it, aside from rethrow by throw;. (Which includes, for example, if that catch block exits through a throw obj;.)

This can be done by putting data to be saved on the heap, and implementing swap (move in C++0x) on your private data inside the exception, for example.

Of course, you need to be careful when using the heap with exceptions… but then again, in most modern OSes, memory overcommitment completely prevents new from ever throwing, for better or for worse. A good memory margin and dropping exceptions from the chain upon complete meltdown should keep it safe.

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;
            throw;
        } catch ( chained_exception &prev ) {
            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() {
        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 ) {
                std::cerr << ep->what() << std::endl;
            }
        }
    }

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

int main() try {
    throw chained_exception(); // create dummy end-of-chain
} catch( chained_exception & ) {
    // body of main goes here
    f();
}

output (appropriately grumpy):

bah
humbug!
meh!
爱本泡沫多脆弱 2024-09-22 12:07:19

您可能想看看这个: http: //www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html

这与 MS 在 C# 中所做的方法有些不同,但它似乎符合您的要求。

You may want to look at this: http://www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html

It's somewhat different approach to what MS did in C#, but it seems to match your requirements.

超可爱的懒熊 2024-09-22 12:07:19

另一个想法是将相关数据添加到异常对象中,然后使用裸的 throw; 语句重新抛出它。我认为在这种情况下堆栈信息被保留,因此您仍然会知道异常的原始来源,但测试是一个好主意。

我敢打赌,由于任何堆栈信息是否可用都是由实现定义的,因此,无论在裸露的 throw; 语句之后是否以任何方式保留它,实现都会有更大的差异。

Another idea is to add the relevant data to your exception object then use a bare throw; statement to re-throw it. I think the stack information is retained in this case, and so you'll still know the original source of the exception, but testing would be a good idea.

I bet since whether or not any stack information is available at all is implementation defined, that implementations will vary even more widely in whether or not it's preserved in any way after a bare throw; statement.

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