TinyGSM c++ CRTP implementation



我试图了解https://github.com/vshymanskyy/TinyGSM/tree/master/src的内部结构,并对如何构造类感到困惑。

我特别看到,在TinyGsmClientBG96.h中,他们定义了一个继承多个模板化父类的类。


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中定义的实现。

  • 在这种情况下为什么不使用函数重写?
  • 这个实现背后的想法是什么?
  • 这是c++中的常见模式吗?
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;
};

这是c++中的常见模式吗?

是的,它被称为CRTP -奇怪的重复模板模式。

在这种情况下为什么不使用函数覆盖?

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

这个实现背后的想法是什么?

比如说,我们想要一个具有可重写方法的类层次结构。经典的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,它是一个模板化的类型,因此编译器完全知道它的类型。这将虚函数调用变为直接调用(因此,甚至允许内联)。

最新更新