【问题标题】:TinyGSM c++ CRTP implementationTinyGSM c++ CRTP 实现
【发布时间】:2021-10-30 14:47:29
【问题描述】:

我试图了解 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++ inheritance overriding virtual crtp


    【解决方案1】:

    这是 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&lt;Derived&gt; 实际上是Derived,它是模板化的——因此,编译器完全知道——类型.这会将虚函数调用变为直接调用(因此,甚至允许内联)。

    【讨论】:

    • 更多关于虚函数的信息here,还有更多关于CRTP的信息here。很棒的答案谢谢!
    猜你喜欢
    • 2023-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多