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 中定义的实现。
- 在这种情况下为什么不使用函数覆盖?
- 这个实现背后的想法是什么?
- 这是 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;
};
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
,这是一个模板化的- 因此,编译器完全知道 - 类型。这将虚函数调用变为直接调用(因此,甚至允许内联)。
我正在尝试了解 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 中定义的实现。
- 在这种情况下为什么不使用函数覆盖?
- 这个实现背后的想法是什么?
- 这是 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;
};
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
,这是一个模板化的- 因此,编译器完全知道 - 类型。这将虚函数调用变为直接调用(因此,甚至允许内联)。