【问题标题】:What is the proper way to use mix-ins in C++ to fulfill interface contracts? [duplicate]在 C++ 中使用 mix-ins 来实现接口契约的正确方法是什么? [复制]
【发布时间】:2021-03-26 16:38:32
【问题描述】:

对于这个非常具体的例子,我提前道歉。

假设我有一个“接口”(纯抽象类)Drawable,它需要实现virtual void draw(),以及virtual float compute_z()。假设draw() 应该在屏幕上绘制一些对象,假设compute_z() 应该返回一个深度以允许渲染引擎订购绘制命令(例如,用于早期z 拒绝/减少过度绘制)。

假设我也有一个非常常见的draw()函数实现,它只是存储一个纹理并在屏幕上绘制一个四边形,将四边形的顶点映射到纹理的顶点,假设我公开了那个常见的通过名为SpriteMixin 的混合类实现。

假设我也有一个非常常见的 compute_z() 函数实现,它只返回一些静态值(静态的意思是它不会改变,而不是该术语的许多其他意义之一),并且我通过一个名为 StaticZComputerMixin 的混合类公开它。

现在假设我要创建一个名为StaticSprite 的对象,它继承Drawable 接口并通过两个mixin 实现这两个函数。

最后,假设我有时希望能够在不继承整个Drawable 接口的情况下使用这些mixin。也就是说,能够通过SpriteMixin 渲染简单的四边形纹理可能很有用,但不一定必须能够计算深度(反之亦然)。然而,在其他时间,我确实想继承整个接口,实际上是通过这些混合实现它。

我相信已经找到一个相对简单的解决方案。特别是,我认为为了使用这些 mix-ins 来实现接口,它们必须从接口继承。但是,由于我不总是想从接口继承,我不得不使用一些模板技巧。不过我很犹豫;由于有两个 mixin,我必须使用多重虚拟继承来“将实现委托给姐妹 mixin 类”,如 here 所述:

class Drawable {
public:
    virtual void draw() = 0;
    virtual float compute_z() = 0;
};

template<typename base>
class SpriteMixin : public virtual base {
private:
    texture t;
public:
    SpriteMixin(texture t) : t(t) {}

    void draw() {
        // Draw t here...
    }
};

template<typename base>
class StaticZComputerMixin : public virtual base {
private:
    float z;
public:
    StaticZComputerMixin(float z) : z(z) {}

    float compute_z() {
        return this->z;
    }
};

class StaticSprite : public SpriteMixin<Drawable>, public StaticZComputerMixin<Drawable> {
public:
    StaticSprite(texture t, float z)
        : SpriteMixin<Drawable>(t), StaticZComputerMixin<Drawable>(z){}
};

当然,如果我想继承整个Drawable 接口,我可以做一些基本的附加模板魔法(比如完整的模板特化)来支持它。

我不认为这一定有什么错误。据我所知,这样的“姐妹委托”是虚拟继承的动机之一。但是,我也听说虚拟继承是菱形问题的“hacky”解决方案(这本质上就是菱形问题)。

这个问题有更合适的解决方案吗?

编辑:

我现在意识到我也可以反转继承树,即我可以让模板化 SpriteMixin 继承自模板化 StaticZComputerMixin,后者继承自 StaticSprite 继承自 Drawable。然而,这将需要模板参数的可变参数扩展来正确地将所有构造函数参数向上传递(有点像std::vector::emplace()),因为在这种情况下,您必须能够指定混合的父级 - ins 以模板化的方式,并且那些父母可能有构造函数参数。据我了解,这些是 c++ 中最常见的两个 mixin 实现,如 here 所述(我以前的解决方案对应于 CRTP- mind 解决方案)。但是,正如 Wikipedia 所述,“mixin 是一个包含供其他类使用的方法的类,而不必是这些其他类的父类”,这表明 CRTP 解决方案在技术上一个 mixin 实现。

有首选的解决方案吗?

【问题讨论】:

    标签: c++ interface mixins diamond-problem


    【解决方案1】:

    我不确定这是否能完全解决您的问题,但是像这样将Drawable 拆分成两个单独的接口怎么样?

    class IDrawable {
    public:
        virtual void draw() = 0;
    };
    
    class IComputeZable {
    public:
        virtual float compute_z() = 0;
    };
    
    class StaticZ : public IComputeZable
    {
    public:
        StaticZ(float z) : _z(z) {}
    
        virtual float compute_z() {return _z;}
    
    private:
        const float _z;
    };
    
    class SpriteDrawable : public IDrawable {
    public:
        SpriteDrawable(texture t) : _t(t) {}
    
        virtual void draw() {drawTexture(_t);}
    
    private:
        texture _t;
    };
    
    class StaticZSpriteDrawable : public StaticZ, public SpriteDrawable
    {
    public:
       StaticZSpriteDrawable(float z, texture t) : StaticZ(z), SpriteDrawable(t) {}
    };
    

    这样任何类类型都可以实现IDrawable/compute_z()IComputeZable/draw(),或两者兼而有之,只要它认为合适。

    【讨论】:

    • 这几乎解决了我的问题,但我需要一个能够同时满足draw()compute_z() 多态需求的抽象基类型。因此,无论哪种方式,我都需要一个带有这两种虚拟方法的接口。但是,我仍然可以将接口分成两个,然后只有第三个接口继承这两个单独的接口。但这并没有改变这样一个事实,即我仍然需要使用多重虚拟继承和模板来根据用例告诉 mixins 要继承哪些接口。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 2013-08-18
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多