如何构造清理代码? 我们在一个编译器不支持异常的C++平台上开发应用程序。
当我们调用API时,我们需要检查返回结果中的错误代码。有时,当错误发生时,我们需要进行一些清理。我们定义一个宏CHECK\u ERROR\u GOTO来检查错误代码。所以代码是这样的如何构造清理代码? 我们在一个编译器不支持异常的C++平台上开发应用程序。,c++,c,C++,C,当我们调用API时,我们需要检查返回结果中的错误代码。有时,当错误发生时,我们需要进行一些清理。我们定义一个宏CHECK\u ERROR\u GOTO来检查错误代码。所以代码是这样的 int res = callAPI1(); CHECK_ERROR_GOTO(res, cleanup); A* object1 = callAPI2(); // HERE WE WILL GET ERROR, //BECAUSE GOTO SKIP
int res = callAPI1();
CHECK_ERROR_GOTO(res, cleanup);
A* object1 = callAPI2(); // HERE WE WILL GET ERROR,
//BECAUSE GOTO SKIP INITIALIZATION OF OBJECT1
CHECK_ERROR_GOTO(object1 == NULL, cleanup);
C* object2 = callAPI3(object2);
CHECK_ERROR_GOTO(object2 == NULL, cleanup)
return 0;
cleanup:
//Cleanup code here.
delete object1
delete object2
正如代码所示,我们将通过goto初始化object1,因此我们需要将object1
和object2
放在函数头中,这是不好的,因为我们应该将其放在需要使用的位置。和添加{}来创建局部作用域不起作用,因为清理代码需要使用局部变量
不管怎样,我们可以安排代码,这样我们就不需要在函数的开头放变量initialize了?我喜欢这样做:
int foo()
{
A* object1 = 0; // make sure you initialize those two
C* object2 = 0; // so that further delete calls always work
for (;;)
{
int res = callAPI1();
if (res == -1)
break;
object1 = callAPI2();
if (object1 == 0)
break;
object2 = callAPI3(object1);
if (object2 == 0)
break;
return 0;
}
delete object1;
delete object2;
return -1;
}
然而,并非总是可以像上面那样清晰地表达代码。但当它是,我觉得它很好。它的优点是将清理代码放在一个地方(就像一个goto
)。否则它可能无法维护,如:
// don't do this:
if(object2==0)
{
delete object1;
return 0;
}
// ... more code ...
if(object3==0)
{
delete object2;
delete object1;
return 0;
}
// ... more code ...
if(object4==0)
{
delete object3
delete object2;
delete object1;
return 0;
}
没错,这不是最美妙的代码,但它可以:
int res = callAPI1();
if (!res) /* inverse of CHECK_ERROR_GOTO(res, cleanup); ??? */
{
A * object1 = callAPI2();
if (object1 != null) /* inverse of CHECK_ERROR_GOTO(object1 == NULL, cleanup); */
{
C * object2 = callAPI3(object1); //I suppose you want object1 here and not 2
if (object2 != null) /* inverse of CHECK_ERROR_GOTO(object2 == NULL, cleanup);*/
{
/* if we reached here then both object1 and 2 are created
thus delete both before returning to not get any memory leaks */
delete object1;
delete object2;
return 0;
}
/* if we reached here then the creation of object2 has failed.
Only delete object1 because object2 is not initialized. */
delete object1;
/* do some kind of return here? like -3 */
}
/* do some kind of return here? like -2 */
}
/* do some kind of return here? like -1*/
你说你的编译器不支持异常。但是它支持在早期返回时执行析构函数吗?我当然希望是这样。你应该通过一个简单的测试来确定 这意味着您可以使用智能指针和其他类来释放析构函数中的资源
int res = callAPI1();
if (!res) return -1;
scoped_ptr<A> object1(callAPI2());
if (!object1) return -1;
scoped_ptr<C> object2(callAPI3(object1.get()));
if ((!object2) return -1;
return 0;
注意:
return
语句应该在清理部分之后(除非您以某种方式返回这些对象…事实上,它现在看起来像一个mem泄漏)我真羡慕您必须处理这个绝对讨厌的问题。代码缺少一些分号。为什么不希望在函数开始时初始化它们?似乎是一个奇怪的要求?将所有变量放在函数的开头并不是那么糟糕。C语言最初是这样的,你必须这样做,它是当时的标准。嗯,OP explicit希望它没有A和B的首字母。我知道这是主观的,但这个for+break
在我看来很奇怪。至少goto cleanup
传达了一个清晰的信息,并且可以轻松地与更复杂的代码一起使用。谢谢,这解决了我的大多数问题,我甚至认为这是不可能完成的任务:)。我将此标记为答案,如果清理代码复杂,即使此解决方案也不总是可行的。如果您有复杂的清理代码,您可能需要编写自定义清理类,但始终可以使用RAII。下面是一篇很好的文章,展示了这一点:
template <typename T>
class scoped_ptr {
T* raw;
struct bool_dummy { void fn() {} };
typedef void (bool_dummy::*pseudo_bool)();
scoped_ptr(const scoped_ptr&); // non-copyable
scoped_ptr& operator =(const scoped_ptr&);
public:
scoped_ptr(T* raw) : raw(raw) {}
~scoped_ptr() { delete raw; }
T* get() const { return raw; }
T& operator *() const { return *get(); }
T* operator ->() const { return get(); }
operator pseudo_bool() const { return get() ? &bool_dummy::fn : 0; }
bool operator !() const { return !get(); }
};