可能失败的分配和操作序列的正确模式

时间:2017-08-16 14:05:36

标签: c design-patterns

我有一段看起来像这样的代码(这只是为了显示结构而简化):

int Ugly_Calculation(void)
{
    type1 *X1 = type1_new();
    if (!X1) return 0;
    type2 *X2 = type2_new(X1);
    if (!X2) return 0;
    type3 *X3 = type3_new(X1, X2);
    if (!X3) return 0;
    int result = PerformCalculation(X1, X2, X3);
    free(X3);
    free(X2);
    free(X1);
    return result;
}

它分配一些对象并将它们用于某些计算并释放对象。 typeX_new 函数经常返回零,因此通常不会正确释放内存。

在我的第一次迭代中,我以下列方式重写了代码:

typedef struct
{
    type1 *X1;
    type2 *X2;
    type3 *X3;
} ugly_context;

void free_ctx(ugly_context *ctx)
{
    free(ctx->X1);
    free(ctx->X2);
    free(ctx->X3);
}

int Ugly_Calculation(void)
{
    ugly_context ctx = {NULL, NULL, NULL};

    ctx->X1 = type1_new();
    if (!ctx->X1)
    {
        free_ctx(&ctx);
        return 0;
    }
    ctx->X2 = type1_new(ctx->X1);
    if (!ctx->X2)
    {
        free_ctx(&ctx);
        return 0;
    }
    ctx->X3 = type1_new(ctx->X1, ctx->X2);
    if (!ctx->X3)
    {
        free_ctx(&ctx);
        return 0;
    }
    int result = PerformCalculation(X1, X2, X3);
    free_ctx(&ctx);
    return result; 
}

我的第一个修复是安全的,但它已经失去了原始代码的可读性。 我的第二个想法是:

type2 *type2_new_check(type1 *X1)
{
   type2 *result = NULL;
   if (X1)
   {
       result = type2_new(X1);
   }
   return result;
}

type3 *type3_new_check(type1 *X1, type2 *X2)
{
   type3 *result = NULL;
   if (X1 && X2)
   {
       result = type3_new(X1, X2);
   }
   return result;
}

int PerformCalculation_check(type1 *X1, type2 *X2, type3 *X3)
{
    int result = 0;
    if (X1 && X2 && X3)
    {
       result = PerformCalculation(X1, X2, X3);
    }
    return result;
}

int Ugly_Calculation(void)
{
    type1 *X1 = type1_new();
    type2 *X2 = type2_new_check(X1);
    type3 *X3 = type3_new_check(X1, X2);
    int result = PerformCalculation_check(X1, X2, X3);
    free(X3);
    free(X2);
    free(X1);
    return result;
}

代码可读性正常,早期退出已经消失,但对于具有3个内存块的方法来说,这是相当长的。

我的真实代码不是3个分配,而是15个,因此该方法变得非常令人毛骨悚然和丑陋。有没有什么好方法,如何在没有过多检查和释放的情况下解决这种模式? (最好的方法是将方法分解为通过检查可以清晰管理的较小部分,但我想因某些原因避免这种情况)

2 个答案:

答案 0 :(得分:3)

goto(以及常见的C惯用语)(IMHO)一个好用

    type *obj1 = 0;
    type *obj2 = 0;
    type *obj3 = 0;

    if (!(obj1 = create1())) goto error;
    if (!(obj2 = create2())) goto error;
    if (!(obj3 = create3())) goto error;

    // your logic here

    return 0; // success

error:
    free(obj3);
    free(obj2);
    free(obj1);
    return -1; // failure

或者,如果您需要在成功案例中释放分配的资源,例如:

    type *obj1 = 0;
    type *obj2 = 0;
    type *obj3 = 0;
    int success = -1;

    if (!(obj1 = create1())) goto error;
    if (!(obj2 = create2())) goto error;
    if (!(obj3 = create3())) goto error;

    success = 0; // success

    // your logic here

error:
    free(obj3);
    free(obj2);
    free(obj1);
    return success;

答案 1 :(得分:1)

处理此问题的最佳方法通常是使用几个内部包装函数。将分配与算法分开。将变量初始化为0(NULL)并利用free(NULL)作为明确定义的无操作,这意味着无论哪个函数失败,所有内容都将被清除。

static int allocate (type1** X1, type2** X2, type3** X3)
{
  *X1 = type1_new(); if(*X1 == NULL) return 0;
  *X2 = type2_new(); if(*X2 == NULL) return 0;
  *X3 = type3_new(); if(*X3 == NULL) return 0;
  return 1;
}

int Ugly_Calculation (void)
{
  type1* X1 = 0;
  type2* X2 = 0;
  type3* X3 = 0;

  int result = allocate(&X1, &X2, &X3);
  if(result != 0)
  {
    result = PerformCalculation(X1, X2, X3);
  }

  free(X1);
  free(X2);
  free(X3);

  return result;
}