【问题标题】:How is it possible to overload the throw function while writing a custom exception class in C++?在 C++ 中编写自定义异常类时,如何重载 throw 函数?
【发布时间】:2013-06-01 11:58:42
【问题描述】:

我编写了一个例程来存储回溯和行号和文件名等。这样做的目的是在抛出异常时存储此类数据。但是,我面临的问题是我的例程将从 catch 块中调用,并且最终会将回溯存储到 catch 块中。这是不好的。我只需要添加回溯,直到抛出异常的地方。我不能(显然在 try 块中调用它,因为在这种情况下,即使在没有引发异常的情况下,我最终也会存储回溯)。我还可以始终将回溯存储到 try 块的末尾并在 catch 块中访问它;但是无法知道异常将在 try 块的哪一行被抛出。因此,throw 函数似乎是添加例程调用的好地方。但我不知道该怎么做。请帮我。

如果我的策略似乎本质上是错误的,请随时为我指出一个更好的解决方案。如果问题本身不清楚,请发表评论。

附:我编写了自定义异常类以从 std::runtime_error 继承。

【问题讨论】:

  • 你负责抛出这些异常吗?您可以从 std::exception 派生类,在其构造函数中生成跟踪。
  • @DrewDormann 我确实源自std::runtime_error,它是std::exception 的子类。您能否详细说明`从 std::exception 派生类在其构造函数中生成跟踪`
  • 哪个平台和编译器?
  • @DrewDormann 好的,我想我现在明白你所说的了。但是同样,对构造函数的调用将是一个远离异常位置的调用,我需要那个特定的位置(抛出的位置)。您能建议一种方法吗?
  • @DrewDormann 我知道你是对的。我会像你说的那样实施,看看会发生什么。我认为它必须工作。谢谢!

标签: c++ exception exception-handling try-catch backtracking


【解决方案1】:

C++ 没有定义可以覆盖的“抛出函数”。抛出由 C++ 实现处理,没有标准方法可以为每个 throw 插入任何类型的代码。

相反,您可以做的是让您的异常类型在构造时存储当前回溯。

std::string get_backtrace() {
    return "this is the backtrace...";
}

struct backtrace_exception : public std::exception {
    std::string b;

    backtrace_exception() : b(get_backtrace()) {}
};

int main() {
    try {
        throw backtrace_exception();
    } catch(backtrace_exception &e) {
        std::cout << e.b;
    }
}

【讨论】:

  • 没有他可以覆盖的throw函数,看我的回答,下次再证明
【解决方案2】:

您不能重载 throw 运算符。更常用的解决方案是定义一个将异常与回溯记录打包的宏。例如:

#include <string>
#include <iostream>
#include <sstream>
#include <exception>
#include <stdexcept>
#include <type_traits>

template <typename BaseException>
class backtraced_exception : public BaseException {
  private:
    std::string backtrace;
  public:

    template <typename... Args>
    backtraced_exception(const char* aFilename, int aLineNum, Args&&... args) :
      BaseException(std::forward<Args>(args)...) {
      std::stringstream ss;
      ss << "From '" << aFilename << "' at line " << aLineNum << ":\n" 
         << BaseException::what();
      backtrace = ss.str();
    };

    backtraced_exception(const std::exception& e, const char* aFilename, int aLineNum) :
      BaseException(static_cast<const BaseException&>(e)) {
      std::stringstream ss;
      ss << "From '" << aFilename << "' at line " << aLineNum << ":\n" 
         << e.what();
      backtrace = ss.str();
    };

    virtual ~backtraced_exception() noexcept { };

    virtual const char* what() const noexcept {
      return backtrace.c_str();
    };
};

#define THROW_WITH_BACKTRACE(EXCEPTION, ARG1) throw backtraced_exception< EXCEPTION >(__FILE__, __LINE__, ARG1)
// ... and you can create more macros for more arguments...

#define CATCH_WITH_BACKTRACE(EXCEPTION, EXCEPT_NAME) catch(backtraced_exception< EXCEPTION >& EXCEPT_NAME)

#define RETHROW_WITH_BACKTRACE(EXCEPT_NAME) throw backtraced_exception< std::decay< decltype(EXCEPT_NAME) >::type >(EXCEPT_NAME, __FILE__, __LINE__)

像这样使用它:

int main() {
  try {
    try {
      try {
        THROW_WITH_BACKTRACE(std::runtime_error, "This is an example!");
      } CATCH_WITH_BACKTRACE(std::runtime_error, e) {
        std::cout << "First caught this exception:\n" << e.what() << std::endl;
        RETHROW_WITH_BACKTRACE(e);
      };
    } catch(std::runtime_error& e) {  // can also catch normally.
      std::cout << "Got this exception:\n"
                << e.what() << std::endl;
      // and even rethrow again, with backtrace:
      RETHROW_WITH_BACKTRACE(e);
    };
  } catch(std::runtime_error& e) {
    std::cout << "Finally, got this exception:\n"
              << e.what() << std::endl;
  };
};

输出如下:

First caught this exception:
From 'logged_except.cpp' at line 50:
This is an example!
Got this exception:
From 'logged_except.cpp' at line 53:
From 'logged_except.cpp' at line 50:
This is an example!
Finally, got this exception:
From 'logged_except.cpp' at line 59:
From 'logged_except.cpp' at line 53:
From 'logged_except.cpp' at line 50:
This is an example!

此解决方案的另一个好处是,您可以通过简单地根据是否要回溯(例如调试或发布构建)有条件地定义宏来禁用回溯。

上面的示例需要 C++11 特性,但您可能会想出一个没有这些特性的等效解决方案(即可变参数模板、decltype、类型特征等)。

您还可以使用 C++11 异常指针来使这个方案更加方便。

【讨论】:

    【解决方案3】:

    是的,您可以通过覆盖此函数来覆盖 GCC 下的“throw”:

    外部“C” void __cxa_throw (void *throw_exception, void *pvtinfo, void (*dest)(void *))

    完成你的工作后,你应该调用真正的“throw”,因为你应该在 Unix 下使用 'dlsym()' 来获取它的地址,或者通过将 -WL,-wrap,symbole 传递给链接器,就像在这里 GNU gcc/ld - wrapping a call to symbol with caller and callee defined in the same object file 一样,我使用了这两种方法

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多