所以,我跑过使用返回值的复合结构和异常的这个(恕我直言)非常不错的主意- Expected<T>
它克服了错误处理(异常,错误代码)的传统方法诸多缺点。
见安德烈Alexandrescu的的谈话(系统误差在C ++处理)和它的幻灯片 。
异常和错误代码已基本与返回的东西的功能,并且不需要的那些相同的使用场景。 Expected<T>
在另一方面,似乎仅定位在有返回值的函数。
所以,我的问题是:
- 有任何你试过
Expected<T>
在实践中? - 你将如何运用这个成语函数返回什么(也就是无效的功能)?
更新:
我想我要澄清我的问题。 该Expected<void>
专业化是有道理的,但我更感兴趣的是它如何被使用-在一致的使用成语。 实施本身是次要的(易)。
例如,Alexandrescu的给出了该示例(一个位编):
string s = readline();
auto x = parseInt(s).get(); // throw on error
auto y = parseInt(s); // won’t throw
if (!y.valid()) {
// ...
}
此代码是,它只是自然流动的方式“干净”。 我们需要的价值 - 我们得到它。 然而,随着expected<void>
人们必须捕获返回的变量和在其上(如执行一些操作.throwIfError()
或东西),这是不一样优雅。 很显然, .get()
没有任何意义与无效。
所以,将你的代码是什么样子,如果你有其他功能,比如说toUpper(s)
其修改就地字符串,并没有返回值?
有任何您是否尝试过预期; 在实践中?
这是很自然的,我用它我之前也看到了这次谈话。
你将如何运用这个成语函数返回什么(也就是无效的功能)?
在幻灯片中显示的形式有一些微妙的影响:
- 唯一的例外是绑定到值。
- 这没关系,你想处理异常。
- 如果忽略了因为某些原因值,异常被抑制。
这不成立,如果你已经expected<void>
,因为既然无人问津的void
值例外总是被忽略。 我将迫使这是我将迫使读取从expected<T>
在Alexandrescus类,有断言和明确suppress
成员函数。 重新抛出从析构函数的异常是不允许有充分的理由,所以它必须与断言完成。
template <typename T> struct expected;
#ifdef NDEBUG // no asserts
template <> class expected<void> {
std::exception_ptr spam;
public:
template <typename E>
expected(E const& e) : spam(std::make_exception_ptr(e)) {}
expected(expected&& o) : spam(std::move(o.spam)) {}
expected() : spam() {}
bool valid() const { return !spam; }
void get() const { if (!valid()) std::rethrow_exception(spam); }
void suppress() {}
};
#else // with asserts, check if return value is checked
// if all assertions do succeed, the other code is also correct
// note: do NOT write "assert(expected.valid());"
template <> class expected<void> {
std::exception_ptr spam;
mutable std::atomic_bool read; // threadsafe
public:
template <typename E>
expected(E const& e) : spam(std::make_exception_ptr(e)), read(false) {}
expected(expected&& o) : spam(std::move(o.spam)), read(o.read.load()) {}
expected() : spam(), read(false) {}
bool valid() const { read=true; return !spam; }
void get() const { if (!valid()) std::rethrow_exception(spam); }
void suppress() { read=true; }
~expected() { assert(read); }
};
#endif
expected<void> calculate(int i)
{
if (!i) return std::invalid_argument("i must be non-null");
return {};
}
int main()
{
calculate(0).suppress(); // suppressing must be explicit
if (!calculate(1).valid())
return 1;
calculate(5); // assert fails
}
虽然它可能会出现新的人只关注C-ISH语言,对我们这些谁了支持和类型语言的味道,它不是。
例如,在Haskell你有:
data Maybe a = Nothing | Just a
data Either a b = Left a | Right b
凡|
读取或并且第一个元素( Nothing
, Just
, Left
, Right
)只是一个“标签”。 从本质上讲和类型只是歧视工会 。
在这里,你会Expected<T>
是这样的: Either T Exception
与专业化的Expected<void>
这是类似于Maybe Exception
。
像马修M.说,这是一件比较新的C ++,但没有什么新的功能很多语言。
我想在这里补充我的2美分:的部分困难和分歧都可以发现,在我看来,在“程序与功能”的做法。 我想用斯卡拉(因为我既使用Scala和C ++熟悉的,我觉得它有一个设备(选配)更接近Expected<T>
来说明这种区别。
Scala中您有选件[T],其是某些(t)或无。 特别地,也可以有选[单位],这是道德上等同于Expected<void>
。
在Scala中,使用模式是非常相似,周围的2个功能内置:isDefined()和get()。 但它也有一个“地图()”函数。
我喜欢把“地图”为“isDefined +得到”同等功能:
if (opt.isDefined)
opt.get.doSomething
变
val res = opt.map(t => t.doSomething)
“传播”的选项结果
我认为,在这里,在使用和组合选项的这个实用的风格,坐落着回答你的问题:
所以,将你的代码是什么样子,如果你有其他功能,比如说TOUPPER(S),其修改就地字符串,并没有返回值?
就个人而言,我不会修改字符串的地方,或者至少我不会返回任何结果。 我看到Expected<T>
作为“功能性”的概念,即需要的功能性图案,以很好地工作:在toupper(一个或多个)将需要要么返回一个新的字符串,或者修改后返回其自身:
auto s = toUpper(s);
s.get(); ...
或者,具有的Scala状地图
val finalS = toUpper(s).map(upperS => upperS.someOtherManipulation)
如果你不想遵循功能的路线,你可以只使用isDefined /有效和更程序性的方式编写代码:
auto s = toUpper(s);
if (s.valid())
....
如果你遵循这条路线(可能是因为你需要),有一个“空洞与单位”点进行:从历史上看,空不被认为是类型,但“无类型”(无效美孚()是一样认为是帕斯卡尔程序)。 单元(如在功能性语言中使用的)更视为一种类型的含义“的计算”。 所以返回选项[单位]确实让更多的意义,被看作为“计算任选做了什么”。 而在Expected<void>
,无效假设类似的意思:即,当它的工作如预期(在没有特殊情况下),刚刚结束(返回什么)的计算。 至少,IMO!
因此,使用或预期的选项[单位]可以被看作是,也许产生的结果,或者也许不是计算。 链接他们会证明这一点很难:
auto c1 = doSomething(s); //do something on s, either succeed or fail
if (c1.valid()) {
auto c2 = doSomethingElse(s); //do something on s, either succeed or fail
if (c2.valid()) {
...
不是很干净。
地图在斯卡拉使得它一点点清洁
doSomething(s) //do something on s, either succeed or fail
.map(_ => doSomethingElse(s) //do something on s, either succeed or fail
.map(_ => ...)
这是理想的好,但仍远。 在这里,也许单子明显胜出...但这是另一个故事..
我一直在思考同样的问题,因为我看过这部影片。 而且到目前为止,我没有发现任何有说服力的论据已经预计,对我来说,它看起来可笑和反对的清晰度和洁净度。 我想出了迄今为止以下几点:
- 所预期的那样好,因为它有任何价值或例外,我们不会被迫使用try {}赶上()为每一个功能,该功能可抛出。 所以用它为每一个扔函数有返回值
- 不扔的每个函数应标有
noexcept
。 每一个。 - 不返回任何内容并没有被标记为每个函数
noexcept
应该尝试包裹{}赶上{}
如果这些语句持有那么我们有自我记录容易使用的接口只有一个缺点:我们不知道什么异常可以在不偷看实施细则抛出。
预计强加一些开销的代码,因为如果你在你的类实现的胆量一些异常(如深私有方法内),那么你应该抓住它在你的接口方法,并返回预期的。 虽然我觉得这是很容忍的具有返回什么一个概念的方法,我相信它带来的混乱和杂波,其设计有没有返回值的方法。 除了对我来说是很自然的,从东西是不应该返回任何回报的事情。
它应与编译器的诊断处理。 基于一定的标准库构建的预期用途很多编译器已经发出警告诊断。 他们应该忽略一个发出警告expected<void>
。