Как явным образом вызывать метод исключения исключений в С++?
У меня есть простой класс:
class A {
public:
bool f(int* status = nullptr) noexcept {
if (status) *status = 1;
return true;
}
void f() {
throw std::make_pair<int, bool>(1, true);
}
};
int main() {
A a;
a.f(); // <- Ambiguity is here! I want to call 'void f()'
}
Я хочу разрешить неоднозначность вызова метода в пользу метода throw-throw любым способом.
Обоснование такого интерфейса:
- Чтобы иметь интерфейс
noexcept(true)
и noexcept(false)
,
- Чтобы дополнительно получить дополнительную информацию с помощью указателя в варианте
noexcept(false)
, в то время как вариант noexcept(true)
всегда будет упаковывать эту информацию в исключение.
Возможно ли вообще? Предложения по улучшению интерфейса также приветствуются.
Ответы
Ответ 1
Наличие функций с такими сигнатурами, очевидно, плохой дизайн, как вы узнали. Реальные решения состоят в том, чтобы иметь разные имена для них или потерять аргумент по умолчанию и были представлены уже в других ответах.
Однако, если вы застряли с интерфейсом, который вы не можете изменить или просто для удовольствия, вот как вы можете явно вызвать void f()
:
Фокус в том, чтобы использовать кастинг для указателя функции для устранения двусмысленности:
a.f(); // <- ambiguity is here! I want to call 'void f()'
(a.*(static_cast<void (A::*)()>(&A::f)))(); // yep... that the syntax... yeah...
Хорошо, поэтому он работает, но никогда не пишут такой код!
Есть способы сделать его более читаемым.
Используйте указатель:
// create a method pointer:
auto f_void = static_cast<void (A::*)()>(&A::f);
// the call is much much better, but still not as simple as `a.f()`
(a.*f_void)();
Создайте лямбда или свободную функцию
auto f_void = [] (A& a)
{
auto f_void = static_cast<void (A::*)()>(&A::f);
(a.*f_void)();
};
// or
void f_void(A& a)
{
auto f_void = static_cast<void (A::*)()>(&A::f);
(a.*f_void)();
};
f_void(a);
Я не знаю, нужно ли это лучше. Синтаксис вызова определенно проще, но это может сбивать с толку, поскольку мы переходим от синтаксиса вызова метода к синтаксису вызова свободных функций.
Ответ 2
Обе версии f
имеют разные значения.
Они должны иметь два разных имени:
-
f
для броска, потому что использование этого означает, что вы уверены в успехе, а сбой будет исключением в программе.
-
try_f()
или tryF()
для основанного на ошибке возврата, поскольку использование этого означает, что отказ вызова является ожидаемым результатом.
Два разных значения должны отражаться в дизайне с двумя разными названиями.
Ответ 3
Поскольку это кажется мне принципиально очевидным, я, возможно, что-то упускаю или не могу полностью понять ваш вопрос. Однако, я думаю, это делает именно то, что вы хотите:
#include <utility>
class A {
public:
bool f(int* status) noexcept {
if (status) *status = 1;
return true;
}
void f() {
throw std::make_pair<int, bool>(1, true);
}
};
int main() {
A a;
a.f(); // <- now calls 'void f()'
a.f(nullptr); // calls 'bool f(int *)'
}
Я просто удалил аргумент по умолчанию из варианта noexcept
. По-прежнему можно вызывать вариант noexcept
, передавая nullptr
в качестве аргумента, который кажется совершенно прекрасным способом указать, что вы хотите назвать этот конкретный вариант функции. В конце концов, там будет какая-то синтаксическая маркер, указывающий, какой вариант вы хотите вызвать!
Ответ 4
Я согласен с предложениями других пользователей, чтобы просто удалить аргумент по умолчанию.
Сильным аргументом в пользу такого дизайна является то, что он будет соответствовать новой библиотеке файловой системы С++ 17, функции которой обычно предлагают вызывающим абонентам выбор между исключениями и ссылочными параметрами ошибок.
См. например std::filesystem::file_size
, который имеет две перегрузки, одна из которых noexcept
:
std::uintmax_t file_size( const std::filesystem::path& p );
std::uintmax_t file_size( const std::filesystem::path& p,
std::error_code& ec ) noexcept;
Идея этого дизайна (первоначально из Boost.Filesystem) почти идентична вашей, за исключением аргумента по умолчанию. Удалите его, и вы сделаете это как новый компонент стандартной библиотеки, который, очевидно, можно ожидать, что он не будет иметь полностью сломанный дизайн.
Ответ 5
В С++ 14 это неоднозначно, потому что noexcept
не является частью сигнатуры функции. С этим сказал...
У вас очень странный интерфейс. Хотя f(int* status = nullptr)
помечен noexcept
, потому что у него есть близнец, который генерирует исключение, вы на самом деле не предоставляете вызывающей стороне логическую гарантию исключения. Кажется, вы одновременно хотите, чтобы f
всегда преуспевал, бросая исключение, если предварительное условие не выполняется (статус имеет допустимое значение, т.е. nullptr
). Но если f
выбрасывает, в каком состоянии находится объект? Видите ли, ваш код очень трудно понять.
Я рекомендую вам взглянуть на std::optional
. Это даст понять читателю, что вы на самом деле пытаетесь сделать.
Ответ 6
У С++ уже есть тип, специально используемый в качестве аргумента для устранения неоднозначности между металическими и небрасывающими вариантами функции: std::nothrow_t
. Вы можете использовать это.
#include <new>
class A {
public:
bool f(std::nothrow_t, int* status = nullptr) noexcept {
if (status) *status = 1;
return true;
}
void f() {
throw std::make_pair<int, bool>(1, true);
}
};
int main() {
A a;
a.f(); // Calls 'void f()'
a.f(std::nothrow); // Calls 'void f(std::nothrow_t, int*)'
}
Хотя я бы предпочел интерфейс, в котором имя отличает варианты, или, возможно, одно, где различие не требуется.
Ответ 7
Здесь чисто метод времени компиляции.
Это может быть полезно, если у вашего компилятора возникли проблемы с оптимизацией вызовов указателей функций.
#include <utility>
class A {
public:
bool f(int* status = nullptr) noexcept {
if (status) *status = 1;
return true;
}
void f() {
throw std::make_pair<int, bool>(1, true);
}
};
template<void (A::*F)()>
struct NullaryFunction {
static void invoke(A &obj) {
return (obj.*F)();
}
};
int main() {
A a;
// a.f(); // <- Ambiguity is here! I want to call 'void f()'
NullaryFunction<&A::f>::invoke(a);
}
Ответ 8
Итак, вы пытаетесь выдать исключение, если код не готов для возврата ошибки?
Затем, как насчет
class ret
{
bool success;
mutable bool checked;
int code;
public:
ret(bool success, int code) : success(success), checked(false), code(code) { }
~ret() { if(!checked) if(!success) throw code; }
operator void *() const { checked = true; return reinterpret_cast<void *>(success); }
bool operator!() const { checked = true; return !success; }
int code() const { return code; }
};
Это все равно Отвращение к Нуггану.
Удалив проверку if(!success)
в деструкторе, вы можете сделать код броском всякий раз, когда код возврата не просматривается.