【问题标题】:Functional template programming instead of inheritance功能模板编程而不是继承
【发布时间】:2018-08-13 02:37:05
【问题描述】:

我目前正在编写一段 C++11 代码:

class A {
public:
    //.. generic public methods which call updateInternalState from time to time ..
private:
    void updateInternalState();
    B internalState;
};

现在我想要有几个版本的 A 类,除了 updateInternalState() 方法之外,它们都是相同的,它会有所不同,并且至少有 3 个不同的版本,做不同的事情,将来可能会有更多。这听起来几乎是一个使用基类继承的好地方,但我想知道是否有一个模板元编程版本,例如:

#include <functional>
template <std::function<void()> updateInternalState>
class A {
public:
//.. generic public methods, which call updateInternalState from time to time ..
private:
    B internalState;

然后我只需要在其他地方定义函数并显式实例化我想要的 A 版本。

我认为最大的问题是 updateInternalState 函数需要访问 A 的私有成员。我认为这可以通过将其声明为 A 的朋友或存储 std::function 类型的成员来解决并将模板参数分配给它。

有人对这种方法有经验并有任何建议吗? 这是一个糟糕的想法吗?我是否应该回到继承(我并不真正想要,因为项目的其余部分是用通用编程范式编写的。)

【问题讨论】:

  • 它们有不同的功能,你的要求是什么?
  • 听起来你有两个类:一个容器和一个应该知道如何更新其内部状态的包含对象。

标签: c++ c++11 templates functional-programming


【解决方案1】:

如果更新函数只使用internalState,则可以简单地将std::function&lt;void(B&amp;)&gt;存储为成员,在构造过程中传递:

class A
{
public:
  template <typename F>
  A(F&& func)
  : updateFunc(std::forward<F>(func))

  void doSomething()
  {
    updateFunc(internalState);
  }

private:
  using UpdateFunc = std::function<void(B&)>;
  B internalState;
  UpdateFunc updateFunc;
};

使用这种方法,您可以获得很好的灵活性,同时仍然保持单一类型而不是整个层次结构。

基于模板的解决方案在这里可能不是一个好主意 - 您只需要自定义一个单个函数,但是将其设为类模板会导致为每个不同的函数生成整个类一个论点。唯一的好处是你可以专门化(或部分专门化)逻辑,但听起来你不需要。

使用继承或存储更新功能作为成员。

【讨论】:

    【解决方案2】:

    这将是一个完整的学术答案:-)

    第一句话:你想做的根本没有意义!

    一步一步:

    你可以像这样使用函数指针作为模板参数:

    using FUNCPTR_T = void(*)();
    
    
    template <  FUNCPTR_T f >
    class A {
    public:
        void DoSomething()
        {
            (*f)();
        }
    };
    
    void f1() { std::cout << "f1" << std::endl; }
    void f2() { std::cout << "f2" << std::endl; }
    
    int main()
    {
        A<f1> a1;
        A<f2> a2;
    
        a1.DoSomething();
        a2.DoSomething();
     }
    

    但是如果你想给你的函数传递一个参数,它是一个类指针(this),你需要定义一个代表这个的函数指针:

    using FUNCPTR_T = void(*)(!!!POINTER_TO_THE_CLASS!!!);
    

    但是这个类本身是一个模板,它接受一个指向函数的指针,该函数有一个参数,该参数是一个指向一个类的指针,该类接受一个函数的指针.... // 无限递归!

    因此,由于您无法为模板参数提供正确的类型,因此您的尝试将失败。

    正如已经提到的:继承要容易得多,而且效果很好。使用 CRTP 通常用于访问 using 类。使用std::function 更容易,但会将成本转移到运行时。

    【讨论】:

      【解决方案3】:

      您可以存储 std::function 并使用 lambda:查看这个简单的类及其使用示例:

      #include <functional>
      #include <iostream>
      #include <conio.h> // for _getch()
      
      class A {
      private:
          int _state;
          std::function<int()> _updater;
      public:
          A() : _state( 0 ) {}
      
          void addUpdater( std::function<int()> updater ) {
              _updater = updater;
          }
      
          void callUpdater() {
              updateInternalState();
          }
      
          int returnState() const {
              return _state;
          }
      
      private:
          void updateInternalState() {
              _state = _updater();
          }
      };
      
      int main() {
          A a;
          a.addUpdater( []() { return 5; } );
          a.callUpdater();
          std::cout << a.returnState() << std::endl;
          a.addUpdater( []() { return 10; } );
          a.callUpdater();
          std::cout << a.returnState() << std::endl;
      
          _getch();
          return 0;
      }
      

      然后,要跟踪同一类的不同实例,您可以使用映射 &lt;id, this*&gt; 代替继承,其中 id 可以是 int 或字符串。也便于查找。我认为在这种情况下这可能比模板更好,因为如果你模板这个类只有function 不同,它将为每个函数更改生成一个全新的类。是的,由于std::function,它确实将事情转移到运行时,但它似乎更易于实现和管理。

      【讨论】:

      • OP 要求函数指针模板而不是继承,而您使用 lambda/std::function 的运行时方法来回答。我看不到问题的答案!
      • @Klaus 他试图避开inheritance。当他确实提到整个项目是用generic 方法编写的时,我确实错过了最后的部分。我可以解决这个问题。
      猜你喜欢
      • 1970-01-01
      • 2018-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-01
      • 1970-01-01
      相关资源
      最近更新 更多