首页 > 解决方案 > 具有伪继承设计 C++ 的模板

问题描述

这个问题可能属于“想要世界上最好的”,但这是一个真正的设计问题,至少需要一个更好的解决方案。

所需结构: 在此处输入图像描述

按重要性排序,这是让我卡住的要求

在我公司的程序中,我们有一个关键的算法生成器,它依赖于编译时和运行时的多态性,即模板类和虚拟继承。我们让它工作,但它很脆弱,难以阅读和开发,并且具有某些无法在更高优化级别上工作的特性(这意味着我们在某处依赖未定义的行为)。代码的简要概述如下。

// Math.hpp
#include <dataTypes.hpp>

// Base class. Actually handles CPU Version of execution
template <typename T>
class Math {
    // ...

    // Example function. Parameters vary in type and number
    // Variable names commented out to avoid compile warnings
    virtual void exFunc ( DataType<T> /*d*/, float /*f*/ )
    {
        ERROR_NEED_CODE; // Macro defined to throw error with message
    }

    // 50+ other functions...
};

//============================================================
// exampleFuncs.cpp
#include<Math.hpp>

template <> void Math<float>::exFunc ( DataType<float> d, float f)
{
    // Code Here.
}

我们已经看到了一些问题,但我们还没有解决主要问题。由于此类中的函数数量众多,我们不想在头文件中定义所有函数。模板功能因此丢失。其次,对于带有模板类的虚函数,无论如何我们都需要在类中定义每个函数,但我们只是抛出一个错误并返回垃圾(如果需要返回)。

//============================================================
// GpuMath.hpp
#include <Math.hpp>

// Derived class. Using CUDA to resolve same math issues
GpuMath_F : Math<float> { ... };

这里的功能相对简单,但我再次注意到,我们放弃了模板功能。我不确定它是否需要这样,但以前的开发人员觉得必须为每种需要的类型声明一个新类(目前是 3 个。乘以 50 个左右的函数,我们有严重的开销)。

最后,当需要功能时。我们使用Factory来创建正确的模板类型对象并将其存储在 Math 指针中。

// Some other class, normally template
template <typename T>
class OtherObject {
    Math<T>* math_;

    OtherObject() {
         math_ = Factory::get().template createMath<T> ();
         // ...
    }
    // ...
};

这里省略了工厂。它变得混乱,对我们没有多大帮助。关键是我们将所有版本的数学对象存储在基类中。

您能否为我指出替代继承的其他技术的正确方向?我在寻找策略设计的变体吗?有模板技巧吗?

感谢您的阅读,并提前感谢您的意见。

标签: c++algorithm

解决方案


正如之前多次讨论过的,具有虚拟功能的模板不能很好地结合在一起。最好选择其中一个。

方法 1:助手类

到目前为止,我们拥有的第一个也是最好的选择就是这样做的,即为包装类选择不使用虚拟功能。

class MathHelper {
    Math cpuMath;
    GpuMath gpuMath;
    bool cuda_; //True if gpuMath is wanted

    template <typename T>
    void exFunc ( DataType<T> d, float f )
    {
        if (cuda_)
            gpuMath.exFunc( d, f );
        else
            cpuMath.exFunc( d, f );
    }
    // 50+ functions...
};

首先,您可能已经注意到函数是模板化的,而不是类。它在结构上更方便。

优点

  • 获得对 CPU 和 GPU 类中模板的完全访问权限。
  • 改进了每个功能的自定义。选择什么是默认的。
  • 对先前结构的非侵入性更改。例如,如果这个 MathHelper 刚刚被调用Math,并且我们有CpuMathGpuMath作为实现,实例化和使用几乎可以与上面相同,如果我们让 Factory 处理 MathHelper,则保持完全相同。

缺点

  • 显式 if/else 和每个函数的声明。
  • MathHelper 中每个函数的强制定义以及至少一个其他 Math 对象。
  • 结果,到处都是重复的代码。

方法二:宏观

这个尝试减少上面的重复代码。在某个地方,我们有一个数学函数。

class Math {
    CpuMath cpuMath;
    GpuMath gpuMath;
    // Some sort of constructor
    static Math& math() { /*static getter*/ }
};

此数学助手使用类似于此处所示的考试 1的静态 getter 函数。我们有CpuMath不包含虚函数的基类和派生类GpuMath。同样,模板是在功能级别上的。

然后从那里,任何时候我们想要一个数学函数,我们都使用这个宏:

#define MATH (func, ret...) \
do { \
    if (math.cuda_) \
        ret __VA_OPT__(=) math().cuda.func; \
    else \
        ret __VA_OPT__(=) math().cpu.func; \
} while (0)

优点

  • 删除前一个包装器的重复代码。
  • 再次解锁模板的全部功能

缺点

  • 不像上面的包装器那样可定制
  • 最初更具侵入性。每次访问 Math 函数时,它都必须从val = math_.myFunc(...), 变为MATH (myFunc(...), val)。由于编辑器没有对宏进行良好的错误检查,这可能会在编辑过程中导致许多错误。
  • 基类必须具有派生类的每个函数,因为它是默认的。

同样,如果有任何其他创造性的方式来实现这个设计,我们将不胜感激。无论哪种方式,我都觉得这是一个有趣的练习,并且很想继续从中学习。


推荐阅读