【问题标题】:Best practice C++ metaprogramming: logic flow最佳实践 C++ 元编程:逻辑流程
【发布时间】:2013-08-01 18:24:10
【问题描述】:

也许我已经被 Ruby 宠坏了,但在我看来,如果我有两个使用相同基本逻辑(但细节不同)的函数,我应该只需要编写一次逻辑——并且作为结果,我应该只需要在一个地方维护代码。

这是基本逻辑,我在许多不同的功能中重复使用。更改的部分分别标记为 A、B、C、D、E 和 F。

  if (recursions) {
    while (lcurr || rcurr) {

      if (!rcurr || (lcurr && (lcurr->key < rcurr->key))) {
        // A
        lcurr   = lcurr->next;
      } else if (!lcurr || (rcurr && (rcurr->key < lcurr->key))) {
        // B
        rcurr   = rcurr->next;
      } else { // keys are == and both present
        // C
        lcurr   = lcurr->next;
        rcurr   = rcurr->next;
      }
    }
  } else {
    while (lcurr || rcurr) {
      if (!rcurr || (lcurr && (lcurr->key < rcurr->key))) {
        // D
        lcurr         = lcurr->next;
      } else if (!lcurr || (rcurr && (rcurr->key < lcurr->key))) {
        // E
        rcurr         = rcurr->next;
      } else { // keys == and both left and right nodes present
        // F
        lcurr         = lcurr->next;
        rcurr         = rcurr->next;
      }
    }
  }

函数的返回值也可能不同。如果可能的话,我希望能够在不同的地方添加额外的逻辑。

我意识到这可以通过 C 宏来完成,但它们似乎不是特别易于维护。我也意识到,如果我的矩阵类型使用嵌套的 STL 列表,这可能会更容易。但是 C++11(或旧 C++)中是否有任何功能允许这个逻辑只写一次?也许有人可以用 lambdas 做到这一点?

【问题讨论】:

  • 有哪些不同的细节?发现它们有点困难......你不能写一些更小的例子吗?
  • 没有检查整个逻辑,但reinterpret_cast 几乎总是错误的。另外,我不知道你在做什么,但它似乎过于复杂......你能用简单的语言描述你的算法做什么吗?
  • 我注意到你似乎没有在任何地方使用leftright,除了递归地传递它们。
  • 有一个方法。编写函数和函数模板。在内部,调用其他(或相同)函数和函数模板。基本上,你会在 Ruby 中做同样的事情。你试过吗?如果是,出了什么问题?
  • 在我看来,这是一个使用枚举器的情况,就像在 C# 中一样。不幸的是,您没有使用 C#。

标签: c++ c++11 metaprogramming


【解决方案1】:

我看到的方法是编写回调函数。因此,您将编写一次逻辑部分,就像您在第二个文本块中一样。您还可以定义函数 A、B、C、D、E 和 F。

在您的逻辑函数中,您将传入所需的参数和指向回调函数的指针。然后,在逻辑函数中,您将调用这些回调并将它们需要的参数传递给它们。

老实说,这似乎最终会做得更多。你会为你的逻辑维护一个单一的事实点,但函数指针可能是一个巨大的痛苦,并降低你的代码的可读性。

为了提供尽可能多的信息,举个例子:

int addTwoNumbers(int a, int b) { //A simple adding function
   return a + b; 
}

int subtractTwoNumbers(int a, int b) { //A simple subtracting function
    return a - b;
}

/*
 * This is the fun one. The first argument is a pointer to a function. The other 
 * arguments are the numbers to do math with. They aren't as important.
 * The important part is that, so long as the function declaration matches the one here
 * (so a function that returns an int and takes in two ints as arguments) it can be
 * used by this function
 */
void math(int (*mathFunc)(int, int), int one, int two) {
    cout << *mathFunc(one, two);
}

int main(int argc, char* argv[]) {
    int whichMath = 0; //Assume 1 is add, 2 is subtract
    if(whichMath == 1) {
        math(&addTwoNumbers, 5, 6); //we're going to add 5 and 6
    } else {
        math(&subtractTwoNumbers, 5, 6); // we're going to subtract 5 and 6
    }
}

如果这 NO 有意义,那么欢迎您加入我们这些与函数指针斗争的大军。同样,我会说您应该只编写两个单独的函数,因为您可以看到这会变得多么丑陋。

作为免责声明,我没有编译此代码。我在工作,这些机器上没有 c++ 编译器。

我过去曾大量使用此站点作为函数指针的参考: http://www.newty.de/fpt/fpt.html#defi

【讨论】:

【解决方案2】:

嗯,一种解决方案是抽出一些冗余代码并将其放入模板中,例如

  template<T1, T2, T3>
  bool TESTKEYS(T1 lcurr, T2 rcurr, T3 actor)
  {
    while (lcurr || rcurr) {
      if (!rcurr || (lcurr && (lcurr->key < rcurr->key))) {
        if (actor.TestLeft(....)) return false;
        lcurr         = lcurr->next;
      } else if (!lcurr || (rcurr && (rcurr->key < lcurr->key))) {
        if (actor.TestRight(....)) return false;
        rcurr         = rcurr->next;
      } else { // keys == and both left and right nodes present
        if (actor.TestBoth(....)) return false;
        lcurr         = lcurr->next;
        rcurr         = rcurr->next;
      }
    }
    return true;
  }

您需要自己决定 TestLeft 使用哪些参数等。

【讨论】:

  • 如果具有相似逻辑的函数没有返回相同的类型怎么办? eqeq_r 返回 false,但 merge_rvoid。我想也可以返回LDTypeRDType
【解决方案3】:
template<typename A, typename B, typename C>
void compute (/*some parameters */)
{
   if (recursions) {
     while (lcurr || rcurr) {
       if (!rcurr || (lcurr && (lcurr->key < rcurr->key))) {
         auto aResult = A (lcurr, rcurr);
        lcurr   = lcurr->next;
       } else if (!lcurr || (rcurr && (rcurr->key < lcurr->key))) {
        auto bResult = B (lcurr, rcurr);
       } // ... and so on
       C (aResult, bResult);
    } // ... etc
} 

要调用compute,您需要编写 来代替A 到F 的占位符。实际工作是在每个类的operator()成员函数中完成的。

class A1 {
  public:
    double operator() (SomeType t1, SomeType t2) {
      // do work
    }
};

class A2 {
  public:
    int operator() (SomeType t1, SomeType t2) {
      // do work
    }
};

class B1 {
  public:
    char* operator() (SomeType t1, SomeType t2) {
      // do work
    }
};

class B2 {
  public:
    SomeClass* operator() (SomeType t1, SomeType t2) {
      // do work
    }
};

class C1 {
  public:
    int operator() (double t1, char* t2) {
}

class C2 {
  public:
    int operator() (int t1, SomeClass* t2) {
}

compute<A1, B1, C1>(whatever);
compute<A2, B2, C2>(whatever);

注意 A1 和 B1 返回类型如何匹配 C1 参数,A2、B2 和 C2 也是如此。

auto需要C++11,如果你不能使用,你将不得不做一些额外的工作:

class A1 {
  public:
    typedef double result_type;
    double operator() (SomeType t1, SomeType t2) {
      // do work
    }
};

compute内部

             typename A::result_type aResult = A (lcurr, rcurr);

【讨论】:

  • 啊!谢谢你。很有趣。
  • 可以使用std::function 代替仿函数吗?
  • 是的,但是除了将它们的类型作为模板参数传递之外,您还必须将 std::functions 作为普通函数参数传递。
  • std::function 不能作为模板参数传递?
  • 不,它不能。它的type可以,但函数本身不能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-21
  • 2021-11-08
  • 2011-06-29
  • 2021-06-05
  • 1970-01-01
相关资源
最近更新 更多