我有一个简单的课程:
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()'
}
我想通过任何方式解决方法调用的歧义,转而使用异常抛出方法。
这种界面背后的基本原理:
noexcept(true)
和noexcept(false)
界面,noexcept(false)
变体中的指针选择性地获取额外信息 - 而noexcept(true)
变体将始终将此信息打包在异常中。有可能吗?建议您建立更好的界面。
答案 0 :(得分:42)
使用这种签名功能显然是一个糟糕的设计,因为你已经发现。真正的解决方案是为它们设置不同的名称或丢失默认参数,并已在其他答案中提供。
但是,如果你遇到一个无法改变的界面,或者只是为了它的乐趣,你可以在这里明确地呼叫void f()
:
诀窍是使用函数指针转换来解决歧义:
a.f(); // <- ambiguity is here! I want to call 'void f()'
(a.*(static_cast<void (A::*)()>(&A::f)))(); // yep... that's 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);
我不知道这是否有必要更好。调用语法肯定更简单,但是当我们从方法调用语法切换到自由函数调用语法时,它可能会令人困惑。
答案 1 :(得分:28)
两个版本f
具有不同的含义。
他们应该有两个不同的名字,如:
f
投掷者,因为使用它意味着你对成功充满信心,失败将是该计划的一个例外。try_f()
或tryF()
用于基于错误返回的,因为使用它意味着调用失败是预期的结果。两种不同的含义应该在设计中反映出两种不同的名称。
答案 2 :(得分:15)
因为对我来说这似乎是显而易见的,我可能会遗漏某些内容或者可能无法完全理解您的问题。但是,我认为这完全符合您的要求:
#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
变体,这似乎是一种非常好的方式,表明你想要调用函数的特定变体 - 毕竟,那里必须是一些语法标记,表明你要调用哪个变体!
答案 3 :(得分:5)
我同意其他用户的建议,只需删除默认参数。
支持这种设计的有力论据是它与新的C ++ 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)几乎与你的相同,除了默认参数。删除它,你就像标准库的一个全新组件一样,显然可以预期它不会有完全破坏的设计。
答案 4 :(得分:2)
在C ++ 14中,它不明确,因为noexcept
不是功能签名的一部分。随着那说......
你有一个非常奇怪的界面。虽然f(int* status = nullptr)
标记为noexcept
,但因为它有一个 会抛出异常的双胞胎,所以您并没有真正为调用者提供逻辑异常保证。如果不满足前置条件(状态具有有效值,即不是f
),您似乎同时希望nullptr
始终成功而抛出异常。但是如果f
抛出,那么对象处于什么状态?你知道,你的代码很难推理。
我建议你改为std::optional
。它会向读者发出你实际想要做的事情。
答案 5 :(得分:2)
C ++已经有一个专门用作参数的类型来消除函数的抛出和非抛出变体之间的歧义:!= null
。你可以使用它。
std::nothrow_t
虽然我仍然更喜欢这个名称区分变体的界面,或者可能不需要区别的界面。
答案 6 :(得分:0)
如果你的编译器在优化函数指针调用时遇到了麻烦,那可能会很有用。
#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);
}
答案 7 :(得分:-3)
因此,如果代码没有准备好返回错误,那么您是否尝试抛出异常?
然后,怎么样
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)
检查,只要不查看返回代码,就可以抛出代码。