CRTP模式在数据结构中存储不均匀类型

时间:2017-04-23 10:19:35

标签: c++ design-patterns crtp

我有兴趣了解CRTP。 我想为引擎实现一个组件系统,我不想访问组件统一样式

GetComponent("withThisName");

,而是在编译时(虚幻风格)

GetComponent<FromThisType>();

虽然它很容易实现CRTP但我真的不知道如何在数据结构中管理CRTP派生类而不再引入动态调度。

Wiki描述了一个带形状的例子:

// Base class has a pure virtual function for cloning
class Shape {
public:
    virtual ~Shape() {};
    virtual Shape *clone() const = 0;
};
// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape_CRTP : public Shape {
public:
    virtual Shape *clone() const {
        return new Derived(static_cast<Derived const&>(*this));
    }
};

// Nice macro which ensures correct CRTP usage
#define Derive_Shape_CRTP(Type) class Type: public Shape_CRTP<Type>

// Every derived class inherits from Shape_CRTP instead of Shape
Derive_Shape_CRTP(Square) {};
Derive_Shape_CRTP(Circle) {};

在这个例子中,我仍然可以做类似

的事情
std::vector<Shape*> shapes;

但是还有虚拟功能,这正是我试图摆脱的第一个地方。我的结论是,我可能仍然没有得到正确的CRTP,或者当它被使用时,另一方面,我看到使用它的虚幻引擎,我想要使用它。

1 个答案:

答案 0 :(得分:0)

CRTP习惯用法并不意味着为非同类类提供通用接口。它几乎都是关于静态多态性的,但结果类型彼此完全不同 考虑一下:

template<typename T>
struct CRTP { /* ... */ };

struct A: CRTP<A> {};
struct B: CRTP<B> {};

AB没有任何共同之处,它们是不同的类型,你不能将它们存储在容器中,除非你给它们一个公共接口作为基类(这就是你的建议,甚至如果你不喜欢它。) 它们是同一类模板的两个特化的事实并没有为您提供一种方法来存储它们,只是忽略它们是不同类型的事实。

可能CRTP不是你想要的。相反,请考虑将类型擦除用于您的目的 作为一个最小的工作示例:

#include <type_traits>
#include <utility>
#include <memory>
#include <vector>

class Shape {
    template<typename Derived>
    static std::unique_ptr<Shape> clone_proto(void *ptr) {
        return std::unique_ptr<Shape>(new Derived{*static_cast<Derived *>(ptr)});
    }

public:
    template<typename T, typename... Args>
    static std::enable_if_t<std::is_base_of<Shape, T>::value, std::unique_ptr<Shape>>
    create(Args&&... args) {
        auto ptr = std::unique_ptr<Shape>(new T{std::forward<Args>(args)...});
        ptr->clone_fn = &Shape::clone_proto<T>;
        return ptr;
    }

    std::unique_ptr<Shape> clone() {
        return clone_fn(this);
    }

private:
    using clone_type = std::unique_ptr<Shape>(*)(void *);
    clone_type clone_fn;
};

struct Rectangle: Shape {};
struct Circle: Shape {};

int main() {
    std::vector<std::unique_ptr<Shape>> vec;
    vec.push_back(Shape::create<Rectangle>());
    vec.push_back(Shape::create<Circle>());
    auto other = vec.at(0)->clone();
}

正如您所看到的,在这种情况下,派生类的类型实际上是已擦除,而您从create函数中得到的是Shape,仅此而已。 CircleRectangle s为Shape,您可以轻松创建Shape s的向量。 根本没有虚函数,但仍然是对内部数据成员clone_fn的双重调度,它返回正确的类型并正确克隆对象。

Polymorphism是一种(我说的)语言功能,它实际上允许用户擦除一堆类型并在运行时正确地调度任何函数调用。如果你想在编译时擦除类型,你可以这样做,但它不是(并且它不能完全)免费。此外它没有任何魔力:如果你想要删除一个类型,你需要一个知道类型是什么的中介,并且能够让它恢复正常工作(虚拟方法或静态函数,充当调度员或其他什么,如果你想使用那种类型你不能避免它们。)