Среди новых стандартных средств библиотеки, представленных в C ++ 20, есть std::source_location
- класс, который позволяет нам удобно собирать и обрабатывать информацию, которая ранее была доступна только через макросы, определенные реализацией, такие как __FILE__
или __LINE__
. Насколько я понимаю, его предполагаемое использование заключается в функциях ведения журнала и отчетов об ошибках. Например, функция, объявленная как
void log(const char* message, std::source_location src_loc = std::source_location::current())
, может регистрировать сообщение, включая имя исходного файла и строку из места его вызова. Однако, немного поэкспериментировав, я склонен использовать source_location
в более широком смысле, и мне интересно, будет ли это хорошей практикой.
В качестве примера предположим, что я разрабатываю приложение который может столкнуться с конкретной ошибкой, от которой у него нет разумного способа исправить. Единственное, что нужно сделать, это завершить работу, но я хотел бы обязательно записать некоторую полезную информацию об обстоятельствах, которые привели к ошибке. Я представлю несколько очень упрощенных фрагментов кода, в этом вопросе я хочу сосредоточиться только на сообщении о местонахождении ошибки, поэтому я пропущу подробности о регистрации другой информации или завершении программы.
Скажем, что ошибка всегда возникает в функции foo
. Простой способ сообщить об этом будет:
int foo(double x)
{
// ...
if (error_occurred)
{
log(error_message);
// shutdown
}
// ...
}
Однако это будет сообщать об ошибке с сайта вызова log
, который всегда является той же самой строкой из реализации foo
. Это не очень полезно, особенно если foo
- это функция низкого уровня, которая вызывается несколько раз из разных мест в коде. Было бы более полезно сообщить о местоположении вызова foo
, который привел к ошибке. Я хотел бы сравнить два подхода к этой проблеме.
1. Вызывает исключение
Допустим, у нас есть такие функции, как fun_1
, fun_2
, ... которые вызывают foo
. Мы могли бы изменить foo
на
int foo(double x)
{
// ...
if (error_occurred)
{
throw std::runtime_error{error_message};
}
// ...
}
, а затем внутри fun_1
:
try
{
n = foo(x);
}
catch (const std::runtime_error& error)
{
log(error.what());
// shutdown
}
Таким образом, log
сообщит местоположение блока catch, связанного с вызовом foo
.
2. Распространение source_location
В качестве альтернативы мы могли бы изменить подпись и тело foo
:
int foo(double x, std::source_location src_loc = std::source_location::current())
{
// ...
if (error_occurred)
{
log(error_message, src_loc);
// shutdown
}
// ...
}
Затем, не делая ничего, кроме n = foo(x);
внутри fun_1
, мы получаем тот же отчет, что и в подходе 1. (Даже немного лучше, поскольку он указывает непосредственно на вызов foo
, а не на блок catch
.)
Ни один из этих подходов не является полностью удовлетворительным, поскольку оба они добавляют некоторый шум к коду, но я полагаю, что это неизбежно при обработке ошибок, вопрос в том, как ее минимизировать. Я вижу следующие проблемы:
- Выдача исключения
- Сайты вызовов
foo
сильно загрязнены. Если есть много функций, таких как fun_1
и fun_2
, возможно, каждая из них вызывает foo
несколько раз, и мы хотим точно сообщить, какой из вызовов вызвал ошибку, тогда каждая должна быть оформлена блоками try
/ catch
. .
Распространение
source_location
- Подпись
foo
загрязнена. - Если существует более длинный стек вызовов между
foo
и fun_1
тогда нам придется загрязнять сигнатуры и вызывать сайты всех промежуточных функций.
Я считаю, что подход 1 является довольно стандартной практикой в C ++, но я не смог найти никакой информации об использовании source_location
Я предлагаю. Было бы неплохо? Разумно ли использовать source_location
в качестве параметра в функциях, принадлежащих «производственному коду», а не только для специальных функций обработки ошибок? Может ли это привести к каким-либо проблемам, о которых я не замечаю?