【问题标题】:C++ - Two member functions differ by a single function callC++ - 两个成员函数因单个函数调用而不同
【发布时间】:2021-03-30 10:17:54
【问题描述】:

我正在努力改进我的 C++ 编码,我想问一下以下问题的最有效和最优雅的解决方案是什么:

我实现了一个有两个成员函数的类。他们都填充了一个双打数组。这些函数的主体是相同的,只是内部有一个调用。在其中之一中,我想调用幂函数pow(x[idx], Moment)。另一方面,我想调用另一个类的另一个对象的另一个成员函数。

#include <vector>
#include <cmath>

class Pot {
  public:
    Pot() {}
    virtual double GetPot(const double x) const {return sin(x);}
    virtual ~Pot() {}
}

class BSplOper {
  private:
    const Pot *m_p;
    ... (other private members)
  public:
    BSplOper(const Pot *p) : m_p{p} {}
    virtual ~BSplOper() {}
    void MkMat1();
    void MkMat2();
}

void BSplOper::MkMat1() {
  double val = 0.0;
  for (some nested loops here) {
    ... (some more code - index transformations, etc)
    for (int indx = 0; indx < LargeNumber; indx++) {
      ... (some more nested loops)
      val += pow(x[indx], someconst);
    }
  }
}

void BSplOper::MkMat2() {
  double val = 0.0;
  for (the same code as in MkMat1) {
    ...(the same code as in MkMat1)
    for (int indx = 0; indx < LargeNumber; indx++) {
      ... (the same code as MkMat1)
      val += m_p->GetPot(x[indx]);
    }
  }
}

有没有办法将它实现为单个函数,该函数将有一个参数来决定将在内部调用哪个函数?问题是这个函数会被调用很多次,它的性能真的很重要。它位于一系列嵌套循环中。因此,我不想在里面放一个条件。

我正在考虑使用std::function 作为这个成员函数的参数,它将引用传递给预期的函数。但是,我不确定std::function 的开销。

使用模板化成员函数会更有效吗?类似的东西

template<typename F>
void BSplOper::MkMat(F fnx) {
  double val = 0.0;
  for (the same code as in MkMat1) {
    ...(the same code as in MkMat1)
    for (int indx = 0; indx < LargeNumber; indx++) {
      ... (the same code as MkMat1)
      val += (*fnx)(x[indx]);
    }
  }
}

在这种情况下,调用它的正确语法是什么?还是这两种解决方案都完全错误?感谢您的任何建议。

【问题讨论】:

  • 如果只有两种可能,你可以使用一个名为 pw 的布尔值,例如:val += pw? pow(x[indx],someconst) : m_p-&gt;GetPot(x[indx]);
  • 感谢您的回复。我认为最好避免嵌套循环内部的条件。在过去,我已经看到这种事情对类似应用程序的性能产生了相当大的负面影响。那我觉得最好有两个独立的函数。
  • 您可以使用带有此布尔值的模板。在这种情况下,编译器将在嵌套循环中创建两个没有测试的函数
  • @TUIlover:正式来说会有一个测试,你需要遵守C++语法规则进行测试。但实际上,每个体面的优化器都会发现这一点并消除测试。
  • @MSalters 是的,我的意思是,我没有说清楚

标签: c++ templates function-pointers


【解决方案1】:

使用函数指针的缺点是编译器可能无法进行所有可能的优化,因此您希望在编译时向编译器提供尽可能多的信息。

为此,您需要将该信息作为模板参数而不是函数参数传递。

一种方法是将if constexpr 与模板参数结合使用。

以下代码只是一个快速而肮脏的示例,说明了如何做到这一点。但是您可能想使用其他东西然后bool

struct BSplOper {

    template<bool F>
    void MkMat() {
      double val = 0.0;
      for (some nested loops here) {
        ... (some more code - index transformations, etc)
        for (int indx = 0; indx < LargeNumber; indx++) {
          ... (some more nested loops)
          
          if constexpr(F) {
            val += pow(x[indx], someconst);
          } else {
            val += m_p->GetPot(x[indx]);
          }
        }
      }
    }

    void MkMat1() {
        MkMat<true>();
    }

    void MkMat2() {
        MkMat<false>();
    }
};

if constexpr 在可维护性和语义方面并不是最佳解决方案。但是如何以正确的方式解决这个问题取决于实际的代码和值依赖以及提升时间。

【讨论】:

  • 谢谢。它有效并且表现非常好。如果可以的话,再问一个问题:为什么我会在这里使用除 bool 之外的任何其他东西? bool 的潜在问题是什么?我不打算使用超过两个可能的函数来调用。
  • @michalt 布尔值的问题在于可维护性。 boolean as template agrument 没有提供太多信息。为什么true 会导致val += pow(x[indx], someconst);falseval += m_p-&gt;GetPot(x[indx]); 中也可能是相反的情况。如果您可以在template&lt;bool F&gt; 中找到真正表达这种区别的F 的名称,那么使用布尔值就可以了。如果找不到名称,那么就可读性和可维护性而言,布尔值是一个糟糕的决定。
  • @michalt 在编写代码时,您应该始终考虑在长时间看代码时是否仍然可以轻松理解代码,或者其他不知道代码的人在看它。对于函数,它应该 - 至少粗略地 - 通过仅读取签名(名称和参数)而不仔细查看其代码来清楚函数的作用。
【解决方案2】:

但是,我不确定std::function 的开销

应该类似于虚拟通话。

在这种情况下,调用它的正确语法是什么?

val += (*fnx)(x[indx]); 应该只是 val += fnx(x[indx]);

template <typename F>
void BSplOper::MkMat(F fnx) {
  double val = 0.0;
  for (the same code as in MkMat1) {
    ...(the same code as in MkMat1)
    for (int indx = 0; indx < LargeNumber; indx++) {
      ... (the same code as MkMat1)
      val += fnx(x[indx]);
    }
  }
}

调用类似于

MkMat([&](const auto& elem){ return pow(elem, someconst);});

或者这两种解决方案都完全错误?

它们各有利弊。

它们都处理任何函子,而不仅仅是函数指针。

  • std::function 有运行时开销,但明确表达了期望。
  • 模板没有开销,但没有表现力(鸭子类型)(如果需要,概念 (C++20) 或 SFINAE /static_assert 可能在这方面有所帮助)。它是模板并强制在标头中可见的实现(除非显式实例化)。

有些人建议function_viewstd::function 的非拥有版本,具有std::function 的表现力,没有开销)。 它甚至允许将 cpp 中的功能作为不再模板来实现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-23
    • 1970-01-01
    • 1970-01-01
    • 2013-02-15
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多