TinyGSM c++ CRTP 实现

TinyGSM c++ CRTP implementation

我正在尝试了解 https://github.com/vshymanskyy/TinyGSM/tree/master/src 的内部结构,但对 classes 的构造方式感到困惑。

特别是我在 TinyGsmClientBG96.h 中看到他们定义了一个 class 继承自多个模板化父 classes.


class TinyGsmBG96 : public TinyGsmModem<TinyGsmBG96>,
                    public TinyGsmGPRS<TinyGsmBG96>,
                    public TinyGsmTCP<TinyGsmBG96, TINY_GSM_MUX_COUNT>,
                    public TinyGsmCalling<TinyGsmBG96>,
                    public TinyGsmSMS<TinyGsmBG96>,
                    public TinyGsmTime<TinyGsmBG96>,
                    public TinyGsmGPS<TinyGsmBG96>,
                    public TinyGsmBattery<TinyGsmBG96>,
                    public TinyGsmTemperature<TinyGsmBG96>

很公平。如果我查看其中之一,例如 TinyGsmTemperature,我会发现一些令人困惑的代码。

看起来静态转换已经到位,因此我们可以调用与硬件无关的接口 getTemperature() 并使用 TinyGsmBG96 中定义的实现。

template <class modemType>
class TinyGsmTemperature
{
public:
  /*
   * Temperature functions
   */
  float getTemperature()
  {
    return thisModem().getTemperatureImpl();
  }

  /*
   * CRTP Helper
   */
protected:
  inline const modemType &thisModem() const
  {
    return static_cast<const modemType &>(*this);
  }
  inline modemType &thisModem()
  {
    return static_cast<modemType &>(*this);
  }

  float getTemperatureImpl() TINY_GSM_ATTR_NOT_IMPLEMENTED;
};

Is this a common pattern in c++?

是的,它叫做 CRTP - 奇怪的重复模板模式。

Why not use function overriding in this case?

override 依赖虚拟表,导致额外的运行时开销。

What is the thinking behind this implementation?

比如说,我们想要一个具有可覆盖方法的 class 层次结构。 classic OOP 方法是 virtual 函数。但是,它们并非零成本:当您拥有

void foo(Animal& pet) { pet.make_noise(); }

你不知道(通常)哪个实现已经传递给 foo(),因为你已经从 Dog(或 Cat 或其他东西中删除了它的类型?)到 Animal。因此,OOP 方法使用虚拟表在运行时找到正确的函数。

我们如何避免这种情况?我们可以静态地记住派生类型:

template<typename Derived /* here's where we keep the type */> struct Animal {
    void make_noise() {
        // we statically know we're a Derived - no runtime dispatch!
        static_cast<Derived&>(*this).make_noise();
    }
};
struct Dog: public Animal<Dog /* here's how we "remember" the type */> {
    void make_noise() { std::cout << "Woof!"; }
};

现在,让我们以零成本的方式重写foo()

template<typename Derived> void foo(Animal<Derived>& pet) { pet.make_noise(); }

不像第一次尝试,我们没有擦除从???Animal的类型:我们知道Animal<Derived>实际上是一个Derived,这是一个模板化的- 因此,编译器完全知道 - 类型。这将虚函数调用变为直接调用(因此,甚至允许内联)。