c++中的多级继承(CRTP)

Multilevel inheritance in c++ (CRTP)

本文关键字:CRTP 继承 多级 c++      更新时间:2023-10-16

请帮我解决这个问题。WhiteDragon将调用Dragon::attacks()而不是MonsterImplement::attacks(),这里存在歧义误差。如果我将Dragon更改为从MonsterImplement派生,然后行std::cout << monster->numAttacks << std::endl;不会编译,因为Dragon没有numAttacks数据成员(也不应该编译,因为不同类型的Dragon将具有不同的值(。因此,我需要WhiteDragon来调用Dragon::attacks(),并在其实例化期间调用finalizeMonster()。如果我让Dragon成为Monster的虚拟派生类,WhiteDragon会调用MonsterImplement::attcks((。

#include <iostream>
struct Monster {
    virtual void finalizeMonster() {}
    virtual void attack() {}
};
template <class MONSTER, int NUM>
struct MonsterInt: virtual public Monster {
    static int numAttacks;
   };
template <class MONSTER, int NUM>
int MonsterInt<MONSTER, NUM>::numAttacks = NUM;
template <class BASE, class MONSTER>
struct MonsterImplement: virtual public BASE {
    MonsterImplement() {finalizeMonster();}
    virtual void finalizeMonster() override;
    virtual void attack() override {std::cout << "MonsterImplement::attack()" << std::endl;}
};
struct Dragon: public Monster {  // or Dragon: public MonsterImplement<Monster, Dragon> ?
// but then Dragon will also call the MonsterImplement constructor (when it has no numAttacks member)
    virtual void attack() override {std::cout << "Dragon::attack()" << std::endl;}
};
struct WhiteDragon: public MonsterInt<WhiteDragon, 3>, 
    public MonsterImplement<Dragon, WhiteDragon> {
    WhiteDragon(): MonsterImplement<Dragon, WhiteDragon>() {}
};
template <class BASE, class MONSTER>
inline void MonsterImplement<BASE, MONSTER>::finalizeMonster() {
    MONSTER* monster = static_cast<MONSTER*> (this);
    std::cout << monster->numAttacks << std::endl;
}
int main() {
    WhiteDragon wd;
    wd.attack();
}

(复制自先前的评论。(

视角#1

CRTP旨在提供非动态行为。如果"numAttacks"的值随每个派生类的不同而不同,则这不是"非动态"情况。一个反例是将非静态非虚拟方法int numAttacks() { return 3; }放在派生类中,然后在CRTP基类中添加一些方法(在所有派生类中共享的攻击逻辑(,然后这些方法可以在其派生类上调用numAttacks()方法,而不会引起虚拟函数调用。

示例:

struct Monster
{
    virtual void attack() = 0;
    virtual int getNumAttacks() const = 0;
};
template <struct MONSTER>
struct AttackLogic : virtual public Monster
{
    virtual void attack() override
    {
        /* allowed to call MONSTER::getNumAttacks(), renamed to avoid confusion. */
        int numAttacks = static_cast<MONSTER*>(this).getNumAttacks();
        /* Use the value in attack calculations. */
    }
};
struct Unicorn 
    : virtual public Monster
    , virtual public AttackLogic<Unicorn>
{
    virtual int getNumAttacks() const override
    {
        return 42; // Unicorn is awesome
    }
};

免责声明:代码仅用于解释我的建议。并非用于实际用途。未使用编译器进行测试。我对病毒继承的了解很薄弱,所以上面的示例代码中可能存在错误或违反准则的情况。


您当前的继承链是:(底部在顶部(

  • Monster
    • Dragon
      • MonsterImplement<Dragon, WhiteDragon>
        • WhiteDragon

Monster定义:

  • 虚拟finalizeMonster()//摘要
  • 虚拟attack()//摘要

Dragon定义:

  • virtual attack()//concrete,覆盖Monster.attack((

MonsterImplement<...>定义:

  • 虚拟attack()//混凝土,覆盖Dragon.attack((和Monster.attack((

WhiteDragon定义:

  • (未定义新的虚拟方法(

很明显,"修复错误后",MonsterImplement.attack()将被调用,因为它是Dragon的子类,因此会覆盖它。

一般来说,它只是说当前的继承层次结构设计得很糟糕,没有人能够修复它


透视图#2

通过CRTP模式注入一个静态int很少是值得的。CRTP更适合以一种不会被重写的方式注入一组非静态、非虚拟方法("样板"(,从而避免每个派生类重新实现相同的"样板"。

至少,将静态int numAttacks转换为虚拟功能

virtual int numAttacks() const { throw std::exception(); }

virtual int numAttacks() const = 0;  // abstract

然后在CCD_ 26中提供了返回3的具体实现。

struct WhiteDragon : ... 
{   ... 
    virtual int numAttacks() const override { return 3; } 
    ... 
};

template <class MONSTER, int NUM>
struct MonsterInt: virtual public Monster {
    static int numAttacks;
   };

这门课的目的是什么?它所做的似乎只是给一个类一些攻击,在这种情况下,从怪物身上派生是没有意义的。

template <int NUM>
struct MonsterInt {
    static int numAttacks;
   };

我认为这"修复"了程序,但很难说,因为很难从代码中得出意图。