多级继承在c ++(CRTP) [英] Multilevel inheritance in c++ (CRTP)

查看:163
本文介绍了多级继承在c ++(CRTP)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请帮助我解决这个问题。 WhiteDragon 是调用 Dragon :: attacks()而不是
MonsterImplement: :attacks(),这里有不明确的错误。如果我改变龙到
从MonsterImplement派生,然后行
std :: cout< monster-> numAttacks< std :: endl; 不会编译,因为Dragon没有 numAttacks 数据成员(也不应该,因为不同类型的龙有不同的值)。所以我需要 WhiteDragon 调用 Dragon :: attacks()并调用 finalizeMonster 。如果我让Dragon的虚拟派生类Monster,WhiteDragon调用MonsterImplement :: attacks()代替。

  #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 {//或Dragon:public MonsterImplement< Monster,Dragon> ?
//但是Dragon也会调用MonsterImplement构造函数(当没有numAttacks成员时)
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 *> (这个);
std :: cout<< monster-> numAttacks< std :: endl;
}

int main(){
WhiteDragon wd;
wd.attack()
}


解决方案




(从之前的评论复制。)



透视#1



CRTP意在提供非动态行为。如果numAttacks的值随每个派生类而变化,这不是一种非动态情况。一个反例是将非静态非虚方法 int numAttacks(){return 3; } ,然后在CRTP基类中添加一些方法(所有派生类共享的攻击逻辑),然后可以调用 numAttacks()



例如:

pre> struct Monster
{
virtual void attack()= 0;
virtual int getNumAttacks()const = 0;
};

template< struct MONSTER>
struct AttackLogic:virtual public Monster
{
virtual void attack()override
{
/ *允许调用MONSTER :: getNumAttacks(),重命名以避免混淆。 * /

int numAttacks = static_cast< MONSTER *>(this).getNumAttacks();

/ *使用攻击计算中的值。 * /
}
};

struct Unicorn
:virtual public Monster
,virtual public AttackLogic< Unicorn>
{
virtual int getNumAttacks()const override
{
return 42; // Unicorn is awesome
}
};

免责声明:代码只是为了解释我的建议。不适合实际使用。未使用编译器测试。我对于virutal继承的知识是薄弱的,所以在上面的示例代码中可能会出现错误或指南错误。






继承链是:(基础在顶部)




  • Monster

    • Dragon

      • MonsterImplement< Dragon,WhiteDragon>

        • WhiteDragon








$ b b

怪物定义:




  • virtual finalizeMonster () //摘要

  • virtual attack() //摘要






Dragon 定义:




  • virtual attack() //具体,覆盖Monster.attack()






MonsterImplement< ...> p>


  • virtual attack() //具体,覆盖Dragon.attack attack()






WhiteDragon 定义:




  • (未定义任何新的虚拟方法)






很清楚的是,修正错误后, MonsterImplement.attack() ,因为它是Dragon的一个子类,因此覆盖它。



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






透视#2



注入静态 int 通过CRTP模式很少值得努力。 CRTP更适合于以不会被覆盖的方式注入一组非静态非虚拟方法(样板),这样可以保存每个派生类重新实现相同的样板。



至少将静态 int numAttacks 转换为虚函数

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

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

然后在 WhiteDragon 返回3。

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





Please help me solve this problem. WhiteDragon is to call Dragon::attacks() instead of MonsterImplement::attacks(), and there is ambiguity error here. If I change Dragon to be derived from MonsterImplement, then the line std::cout << monster->numAttacks << std::endl; won't compile because Dragon has no numAttacks data member (nor should it, because different types of Dragons are to have different values). So I need WhiteDragon to call Dragon::attacks() and to call finalizeMonster() during its instantiation. If I make Dragon virtual derived class of Monster, WhiteDragon calls up MonsterImplement::attacks() instead.

#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();
}

解决方案


(Copied from an earlier comment.)

Perspective #1

CRTP is meant to provide non-dynamic behavior. If the value of "numAttacks" vary with each derived class, this is not a "non-dynamic" situation. A counter-example would be to put a non-static non-virtual method int numAttacks() { return 3; } in a derived class, and then in the CRTP base class add some methods (the attack logic that is shared across all derived classes), which can then call the numAttacks() method on its derived class, without incurring a virtual function call.

Example:

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
    }
};

Disclaimer: Code only meant to explain my suggestion. Not intended for practical use. Not tested with compiler. My knowledge of virutal inheritance is weak, so there may be mistakes or broken guidelines in the sample code above.


Your current inheritance chain is: (base at top)

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

Monster defines:

  • virtual finalizeMonster() // abstract
  • virtual attack() // abstract

Dragon defines:

  • virtual attack() // concrete, overrides Monster.attack()

MonsterImplement<...> defines:

  • virtual attack() // concrete, overrides Dragon.attack() and Monster.attack()

WhiteDragon defines:

  • (no new virtual methods defined)

It is very clear that "after fixing the bug", that MonsterImplement.attack() will be called, because it is a subclass of Dragon and therefore overrides it.

In general it only says that the current inheritance hierarchy is badly designed, and that nobody would be able to fix it.


Perspective #2

Injecting a static int through CRTP pattern is rarely worth the effort. CRTP is more suitable for injecting a set of non-static, non-virtual methods ("boilerplate") in a way that will not be overridden, that saves every derived class from re-implementing the same "boilerplate".

At the minimum, convert the static int numAttacks into a virtual function

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

or

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

and then provide a concrete implementation in WhiteDragon to return 3.

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


这篇关于多级继承在c ++(CRTP)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆