假设我想连续调用四个函数,这些函数在我的某个对象上运行。如果其中任何一个失败,我想在不调用其他人的情况下返回FAILURE
,如果所有这些都成功完成,我想返回SUCCESS
。
通常情况下,我会这样做:
if(function_zero(&myMutableObject) == SUCCESS)
{
return FAILURE;
}
if(function_one(&myMutableObject) == SUCCESS)
{
return FAILURE;
}
if(function_two(&myMutableObject) == SUCCESS)
{
return FAILURE;
}
if(function_three(&myMutableObject) == SUCCESS)
{
return FAILURE;
}
return SUCCESS;
或者,如果我需要做一些清理:
if(function_zero(&myMutableObject) == SUCCESS)
{
status = FAILURE;
goto cleanup;
}
if(function_one(&myMutableObject) == SUCCESS)
{
status = FAILURE;
goto cleanup;
}
if(function_two(&myMutableObject) == SUCCESS)
{
status = FAILURE;
goto cleanup;
}
if(function_three(&myMutableObject) == SUCCESS)
{
status = FAILURE;
goto cleanup;
}
cleanup:
// necessary cleanup here
return status;
然而,我正在进行的项目有一些限制:
goto
,永远这导致我这样的事情:
if(function_zero(&myMutableObject) == SUCCESS)
{
if(function_one(&myMutableObject) == SUCCESS)
{
if(function_two(&myMutableObject) == SUCCESS)
{
status = function_three(&myMutableObject);
}
else
{
status = FAILURE;
}
}
else
{
status = FAILURE;
}
}
else
{
status = FAILURE;
}
return status;
不幸的是,这经常使我违背行长限制。
我的问题是:有没有更简单的方式来写这个?
备注&限制:
答案 0 :(得分:7)
使用例外和RAII。这些都是他们发明要解决的问题。例外情况更多的是系统范围的功能,而不是您可以在本地应用的功能。
对于清理块,RAII正是您需要的功能。
对于成功/失败,我们可以使用lambdas和variadics隐式链接它们。
现在我们可以简单地将它们写成列表中的lambda。
status f() {
struct nested {
static template<typename F> status_t Check(F f) {
return f();
}
static template<typename F, typename... Chain> status_t Check(F f, Chain... chain) {
auto status = f();
return status != failure ? Check(chain...) : status;
}
};
return nested::Check(
[] { return function_zero(&myMutableObject); },
[] { return function_one(&myMutableObject); },
[] { return function_two(&myMutableObject); },
[] { return function_three(&myMutableObject); },
);
}
如果您需要捕获返回值,这会变得稍微更多问题,但由于它似乎总是带有out参数的错误代码,如果您只是声明接收变量,那么应该没问题f()
,然后所有未来的lambdas都可以参考它。它也不要求每个函数都具有相同的签名,或者分配各种数据结构。
答案 1 :(得分:5)
如果状态当前设置为SUCCESS,则仅使用&amp;&amp;运营商。如果设置为FAILURE&amp;&amp;将立即失败,后续测试将不会执行。
status = SUCCESS
if (status == SUCCESS && function_zero(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
if (status == SUCCESS && function_one(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
if (status == SUCCESS && function_two(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
if (status == SUCCESS && function_three(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
return status;
正如@Mooing Duck建议的那样,你可以简单地在其他链中做到这一切:
status = SUCCESS
if (function_zero(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
else if (function_one(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
else if (function_two(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
else if (function_three(&myMutableObject) == FAILURE)
{
status = FAILURE;
}
return status;
答案 2 :(得分:3)
我在处理长链C调用(通常是WinAPI)时多次使用的模式如下:
bool ret =
function_zero(&myMutableObject) == SUCCESS
&&
function_one(&myMutableObject) == SUCCESS
&&
function_two(&myMutableObject) == SUCCESS
&&
function_three(&myMutableObject) == SUCCESS;
if(!ret)
{
// cleanup
}
return ret?SUCCESS:FAILURE;
您甚至可以将&&
留在每行的末尾,这样它看起来更像是&#34;正常&#34;电话顺序(虽然我个人更喜欢这种方式,但它更清楚的是什么)。
&&
运算符保证以正确的顺序执行,并且只有先前的调用成功,并在调用之间引入必要的序列点(或者在C ++ 11中调用它们),所以顺序很好地定义了各种调用之间的参数评估。此外,它具有足够低的优先级,不需要额外的括号。
如果你不害怕使用COM风格的宏,你也可以将== SUCCESS
检查封装在一个宏中,比如
// in some header, maybe with a less abused name
#define OK(x) ((x) == SUCCESS)
bool ret =
OK(function_zero(&myMutableObject))
&&
OK(function_one(&myMutableObject))
&&
OK(function_two(&myMutableObject))
&&
OK(function_three(&myMutableObject));
// ...
更好的是,如果SUCCESS != 0
和FAILURE == 0
您完全放弃OK()
和== SUCCESS
,只需使用&&
来关联来电。
答案 3 :(得分:2)
你说没有例外,但我认为你应该知道你放弃了什么。
如果您使用基于RAII的清理并将错误报告为异常而不是代码,那么您将获得如下代码:
function_zero(&myMutableObject);
function_one(&myMutableObject);
function_two(&myMutableObject);
function_three(&myMutableObject);
这是一个解释正确的C ++异常处理及其好处的网站:
一些好处是:
此外,赫伯·萨特(Herb Sutter)就单一退出问题提出了这个问题。 (你的常用名称&#34;没有提前退货&#34;规则):
总的来说,请注意SE / SE是一个过时的想法,而且一直都是 错误。 “单一条目”,或者功能应该始终如一的想法 进入一个地方(在他们的开始),而不是从goto跳跃 调用者的代码直接到函数体内的随机位置,是 并且是计算机科学中非常有价值的进步。它是什么 使图书馆成为可能,因为它意味着你可以打包一个 功能和重用它,功能总是知道它的起始 状态,它开始的地方,无论调用代码如何。 “单出口,” 另一方面,在优化的基础上得到了不公平的欢迎 ('如果有一次返回,编译器可以执行返回值 优化更好' - 见上面反例)和对称性('如果 单一进入是好的,单一退出也必须是好的')但这是错误的 因为原因不能反过来允许呼叫者跳入 是不好的,因为它不受功能的控制,但允许 当它知道它已经完成时,它会自动返回 很好,完全在功能的控制下。
http://herbsutter.com/category/c/gotw/page/4/
您应该尝试更新规则,即使这些规则只适用于新项目。
答案 4 :(得分:1)
您可以将函数指针存储在std :: function的容器中(只要它们具有与示例中相同的签名):
std::vector<std::function<error_code (myobject&)> functions { func1, func2, func3, func4 };
error_code status = SUCCESS;
for (const auto& f : functions) {
if (f(myobject) == ERROR) {
clean_up();
status = ERROR;
break;
}
}
return status;
答案 5 :(得分:1)
这可行:
bool success = true;
success = success && function_zero(&myMutableObject) != FAILURE;
success = success && function_one(&myMutableObject) != FAILURE;
success = success && function_two(&myMutableObject) != FAILURE;
success = success && function_three(&myMutableObject) != FAILURE;
return success ? SUCCESS : FAILURE;
返回可以替换为:
int status = SUCCESS;
if( !success ) status = FAILURE;
return status;
如果贵公司也禁止使用条件操作员。
答案 6 :(得分:0)
您可以编写一个简单的本地仿函数:
bool fn0(int&) { return false; }
bool fn1(int&) { return false; }
bool fn2(int&) { return false; }
int main() {
struct Test {
const bool result;
typedef bool (*function)(int&);
Test(function f, int& value)
: result(f(value))
{};
operator bool () const { return result; }
};
int value;
return Test(fn0, value)
&& Test(fn1, value)
&& Test(fn2, value);
}