Как можно перегрузить функцию throw при написании пользовательского класса исключений в C ++?

Я написал подпрограмму для хранения обратной трассировки и строки № и имени файла и так далее. Цель этого состояла в том, чтобы хранить такие данные для каждого исключения. Однако проблема, с которой я сталкиваюсь, заключается в том, что моя подпрограмма будет вызываться из блока catch, и в итоге она сохранит обратную трассировку до блока catch. Это не хорошо. Я только должен добавить обратный след до места, где выбрасывается исключение. Я не могу (очевидно, вызвать его внутри блока try, поскольку в этом случае я буду хранить обратную трассировку даже в тех случаях, когда исключение не выдается). Я мог бы также всегда сохранить обратную трассировку до конца блока try и получить к нему доступ внутри блока catch; но нет никакого способа узнать, в какую строку блока try будет сгенерировано исключение. Таким образом, функция throw является хорошим местом для добавления обычного вызова. Но я не знаю, как это сделать. Пожалуйста, помогите мне.

Если моя стратегия кажется неправильной, пожалуйста, не стесняйтесь указывать мне лучшее решение. Если сама проблема не ясна, пожалуйста, оставьте комментарий.

Постскриптум Я написал собственный класс исключений для наследования от std :: runtime_error.

1

Решение

Не существует «функции выброса», которую вы можете переопределить. Бросок обрабатывается реализацией 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;
}
}
4

Другие решения

Вы не можете перегрузить оператор броска. Более обычным решением было бы определить макрос, который упаковывает исключение с записью обратной трассировки. Например это:

#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, type-traits и т. Д.).

И вы могли бы также использовать указатели исключений C ++ 11, чтобы сделать эту схему еще более удобной.

1

Да, вы можете переопределить ‘throw’ в GCC, переопределив эту функцию:

extern «C» void __cxa_throw (void * thrown_exception, void * pvtinfo, void (* dest) (void *))

После выполнения ваших вещей вы должны вызвать реальный ‘throw’, для этого вы должны использовать ‘dlsym ()’ в Unix, чтобы получить его адрес, или использовать перенос функции в ‘mingw’, передав -WL, -wrap, symbole в компоновщик, например здесь GNU gcc / ld — упаковка вызова символа с вызывающим и вызываемым абонентами, определенными в одном объектном файле , Я работал с обоими методами

1