C++基于子类类型的动态模板参数

C++ Dynamic template parameter based on subclass type

本文关键字:动态 参数 类型 于子类 子类 C++      更新时间:2024-09-27

我目前正在第一个C++项目中工作,试图通过经验学习语言。这很有挑战性,但到目前为止,我已经通过自己和互联网的帮助解决了大多数问题
然而,最近我遇到了一些问题,我根本找不到令人满意的解决方案,所以我想听听更有经验的程序员的意见。

问题如下:

一方面,我有一个抽象的BaseShape类,从中继承了几个具体的类,如TriangleCircleRectangle
  • 另一方面,我有一个模板RenderCommand<Shape>类,它对每个混凝土形状都有专门的定义
  • 最后,我有一个数组,它包含多个混合形状作为BaseShape指针
  • 我现在的问题是,从这些BaseShape指针创建专用RenderCommand实例的最佳方式是什么?我目前正在考虑动态选角,或者尝试一些虚拟方法的多态性,但感觉都不对。

    编辑
    目前,我已经确定了一个可行的解决方案,但我知道它可能没有遵循几个最佳实践,因此我仍然愿意接受其他建议。

    为了稍微澄清一下,我还添加了一些伪代码来解释类定义的布局。

    RenderCommands.h

    struct BaseRenderCommand
    {
    virtual execute() = 0;
    ... // contains stuff on cameras and other render options
    }
    template<class Shape>
    struct RenderCommand : public BaseRenderCommand
    {
    RenderCommand(Shape *shape) {...};
    virtual execute();
    ...
    }
    

    Shapes.h

    struct BaseShape
    {
    ...  // Typical shape info like center position
    }
    struct Triangle : public BaseShape
    {
    ...
    }
    struct Circle : public BaseShape
    {
    ...
    }
    struct Rectangle : public BaseShape
    {
    ...
    }
    

    以下是一种可能解决此问题的方法。我将把您从运行时构造(polymorphism(转移到编译时构造std::variant

    在没有任何代码的情况下,我不得不在这里做大量的假设。还要注意,我包括了基本Shapeclass,但在我提供的示例中完全没有必要。

    #include <vector>
    #include <variant>
    template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
    template<class... Ts> overload(Ts...) -> overload<Ts...>;
    struct Shape {
    virtual ~Shape() = default;
    };
    struct Square : public Shape {};
    struct Triangle : public Shape {};
    struct Circle : public Shape {};
    template<typename T>
    struct RenderCommand {
    void render(const T&) {}
    };
    /* Your RenderCommand specializations here. */
    using Shapes = std::variant<Square, Triangle, Circle>;
    int main() 
    {
    std::vector<Shapes> shapes {Square{}, Triangle{}, Circle{}};
    for (const auto& shape : shapes) 
    {
    std::visit(overload{
    [](const Square& s) { RenderCommand<Square>{}.render(s); },
    [](const Triangle& t) { RenderCommand<Triangle>{}.render(t); },
    [](const Circle& c) { RenderCommand<Circle>{}.render(c); }
    }, shape);
    }
    }
    

    我继续并决定构建类似于WBuck给出的第一个建议的东西。我忘记在原始答案中包含的一条信息是,RenderCommand<Shape>还继承了一个具有纯虚拟execute()函数的抽象基类BaseRenderCommand

    我决定的解决方案是这样的:

    RenderCommands.h

    struct BaseRenderCommand
    {
    virtual execute() = 0;
    ...
    }
    template<class Shape>
    struct RenderCommand : public BaseRenderCommand
    {
    RenderCommand(Shape *shape) {...};
    virtual execute();
    ...
    }
    

    RenderCommands.cpp

    #include "RenderCommands.h"
    #include "Shapes.h"
    void RenderCommand<Triangle>::execute()
    {
    ...
    }
    void RenderCommand<Circle>::execute()
    {
    ...
    }
    void RenderCommand<Rectangle>::execute()
    {
    ...
    }
    

    Shapes.h

    #include RenderCommands.h
    struct BaseShape
    {
    virtual BaseRenderCommand *create_cmd() = 0;
    ...
    }
    struct Triangle : public BaseShape
    {
    virtual BaseRenderCommand *create_cmd();
    ...
    }
    struct Circle : public BaseShape
    {
    virtual BaseRenderCommand *create_cmd();
    ...
    }
    struct Rectangle : public BaseShape
    {
    virtual BaseRenderCommand *create_cmd();
    ...
    }
    

    Shapes.cpp

    #include "Shapes.h"
    BaseRenderCommand *Triangle::create_cmd()
    {
    return RenderCommand<Triangle>(this);
    }
    BaseRenderCommand *Circle::create_cmd()
    {
    return RenderCommand<Circle>(this);
    }
    BaseRenderCommand *Rectangle::create_cmd()
    {
    return RenderCommand<Rectangle>(this);
    }
    

    main.cpp

    #include <vector>
    #include "RenderCommands.h"
    #include "Shapes.h"
    std::vector<BaseShape*> shapes = new std::vector<BaseShape*>();
    std::vector<BaseRenderCommand*> renderCmds = new std::vector<BaseRenderCommand*>();
    ...
    for (auto shape : shapes)
    {
    renderCmds.push_back(shape->create_cmd())
    }
    

    您似乎对命令模式有一些误解。使用命令模式并不意味着您需要使用class CommandCommand::Execute()之类的东西。您所需要的只是一个std::function或一个lambda。

    你不应该有Shape::create_cmd()。相反,命令应该是Shape类中的一个可赋值变量,这样您就可以向它添加不同的命令,并通过Shape::execute()执行它们。

    这里有一些你可以尝试的东西:

    形状:

    struct Shape
    {
    std::string name;
    std::function<void(Shape*)> command = [](Shape*){};
    void execute() { command(this); }
    };
    struct Circle: Shape {
    explicit Circle(double radius)
    : Shape{"circle"}
    , radius{radius}
    {}
    double radius;
    void setCommand(std::function<void(Circle*)> const& command) {
    this->command = [command](Shape* shape){command(static_cast<Circle*>(shape));};
    }
    };
    struct Triangle: Shape {
    Triangle(double base, double height)
    : Shape{"triangle"}
    , base{base}
    , height{height}
    {}
    double base;
    double height;
    void setCommand(std::function<void(Triangle*)> const& command) {
    this->command = [command](Shape* shape){command(static_cast<Triangle*>(shape));};
    }
    };
    

    命令:

    template<std::derived_from<Shape> T>
    void render(T*);
    template<>
    void render(Circle* circle)
    {
    std::cout << circle->name << " is round!n";
    }
    template<>
    void render(Triangle* triangle)
    {
    std::cout << triangle->name << " is sharp!n";
    }
    template<std::derived_from<Shape> T>
    void print_area(T*);
    template<>
    void print_area(Circle* circle)
    {
    std::cout << "Circle area: " << circle->radius * circle->radius * 3.14 << 'n';
    }
    template<>
    void print_area(Triangle* triangle)
    {
    std::cout << "Triangle area: " << triangle->height * triangle->base / 2 << 'n';
    }
    
    int main()
    {
    Circle circle(2);
    Triangle triangle(3, 10);
    auto vec = std::vector<Shape*>{&circle, &triangle};
    std::cout << "Nothing happens here:n";
    for (const auto& shape : vec)
    {
    shape->execute();
    }
    circle.setCommand(render<Circle>);
    triangle.setCommand(render<Triangle>);
    std::cout << "nRender:n";
    for (const auto& shape : vec)
    {
    shape->execute();
    }
    circle.setCommand(print_area<Circle>);
    triangle.setCommand(print_area<Triangle>);
    std::cout << "nPrint area:n";
    for (const auto& shape : vec)
    {
    shape->execute();
    }
    }