我有一个返回自定义类结构的函数,但是如何处理我希望通知用户函数失败的情况,如返回Cell CSV::Find(std::string segment) {
Cell result;
// Search code here.
return result;
}
。
我的功能看起来像这样:
Cell.data
因此,当成功时,它会返回正确的结果,但是当它失败时我该如何处理?
我考虑在Cell中添加一个布尔方法来检查Cell.IsEmpty()
是空的(onSuspendCanceled
)。但我是否以过于复杂的方式思考这个问题?
答案 0 :(得分:41)
有三种一般方法:
std::optional<Cell>
(或其他可能包含或不包含实际Cell
的类型。)bool
,并添加Cell &
参数。其中哪一个最好取决于您打算如何使用此功能。如果主要用例传递有效的段,那么一定要使用例外。
如果此功能的部分设计是可用于判断某个段是否有效,则异常不合适,我首选的选择是std::optional<Cell>
。这可能不适用于您的标准库实现(这是C ++ 17的功能);如果没有,boost::optional<Cell>
可能有用(如Richard Hodges的回答中所述)。
在评论中,代替std::optional<Cell>
,用户您建议使用expected<Cell, error>
(不是标准C ++,但建议用于未来的标准,并且可以在std
命名空间之外实现)。如果有多种可能的原因,这可能是一个很好的选择,可以添加一些指示,说明为什么没有找到Cell
传入的segment
参数。
第三个选项我主要包括完整性。我不推荐它。它是其他语言中流行且通常很好的模式。
答案 1 :(得分:7)
这个函数是一个查询,它可以有效地找不到单元格,还是一个必要条件,预计会找到单元格?
如果是前者,则返回一个可选的(或可以为空的指针)单元格。
如果是后者,如果找不到则抛出异常。
前任:
boost::optional<Cell> CSV::Find(std::string segment) {
boost::optional<Cell> result;
// Search code here.
return result;
}
后期: 就像你拥有它一样。
当然还有基于c ++ 17变体的方法:
#include <variant>
#include <string>
struct CellNotFound {};
struct Cell {};
using CellFindResult = std::variant<CellNotFound, Cell>;
CellFindResult Find(std::string segment) {
CellFindResult result { CellNotFound {} };
// Search code here.
return result;
}
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
void cellsAndStuff()
{
std::visit(overloaded
{
[&](CellNotFound)
{
// the not-found code
},
[&](Cell c)
{
// code on cell found
}
}, Find("foo"));
}
答案 2 :(得分:4)
处理严重失败的C ++方法是定义一个形式的异常类:
struct CSVException : std::exception{};
在你的函数中,然后throw
失败分支中的一个:
Cell CSV::Find(std::string segment) {
Cell result;
// Search code here.
if (fail) throw CSVException();
return result;
}
然后,您在主叫站点使用try
catch
块处理失败案例。
然而,如果&#34;失败&#34;分支是正常行为(主观确实,但只有你可以判断正常性),然后确实在Cell
内填充某种失败指标,或者甚至可能将返回类型改为std::optional<Cell>
。
答案 3 :(得分:0)
如果您可以使用C ++ 17,另一种方法是使用std::optional
类型作为返回值。这是一个可能包含或不包含值的包装器。然后,调用者可以检查您的函数是否实际返回了一个值,并处理它没有的情况。
std::optional<Cell> CSV::Find(std::string segment) {
Cell result;
// Search code here.
return result;
}
void clientCode() {
auto cell = CSV::Find("foo");
if (cell)
// do stuff when found
else
// handle not found
}
答案 4 :(得分:0)
另一个选项是使用多个返回值:
std::pair<Cell, bool> CSV::Find(std::string segment) {
Cell result;
// Search code here.
return {result, found};
}
// ...
auto cell = CSV::Find("foo");
if (cell->second)
// do stuff with cell->first
布尔标志表示是否找到了请求的Cell
。
std::map::insert
); first
和second
的模糊性要求始终记住对中值的相对位置(C ++ 17 structured bindings部分解决了此问题); 答案 5 :(得分:0)
对于解析,通常最好避免std::string
而是使用std::string_view
;如果C ++ 17不可用,那么最小功能的版本就可以轻松实现。
此外,不仅要跟踪解析的内容,还要追踪余下的。
跟踪剩余部分有两种可能性:
我个人更喜欢后者,因为在出现错误的情况下,它可以保证调用者手中有一个未修改的值,这对错误报告非常有用。
然后,您需要检查可能发生的错误,以及您希望的恢复机制。这将告知设计。
例如,如果您希望能够解析格式错误的CSV文档,那么Cell
能够表示格式错误的CSV单元是合理的,在这种情况下,界面非常简单:< / p>
std::pair<Cell, std::string_view> consume_cell(std::string_view input) noexcept;
函数始终前进且Cell
可能包含正确的单元格或格式不正确的单元格。
另一方面,如果您只希望支持格式良好的CSV文档,那么通过异常发出错误信号并且Cell
只能保存实际单元格是合理的:
std::pair<std::optional<Cell>, std::string_view> consume_cell(...);
最后,您需要考虑如何发出行结束条件的信号。它可能是Cell
上的简单标记,但此时我个人更喜欢创建迭代器,因为它提供了更自然的界面,因为行是Cell
的范围。
迭代器的C ++接口有点笨重(因为你需要一个“结束”,并且在解析之前结束是未知的),但是我建议坚持使用它以便能够使用带有for
循环的迭代器。但是,如果您希望偏离它,至少可以使用while
轻松使用它,例如std::optional<Cell> cell; while ((cell = row.next())) { ... }
。