使用 std::variant 强制使用通用接口,无需继承

2024-02-20

假设你有一些类似的课程Circle, Image, Polygon为此,您需要强制执行一个如下所示的通用接口(不是真正的代码):

struct Interface {
    virtual bool hitTest(Point p) = 0;
    virtual Rect boundingRect() = 0;
    virtual std::string uniqueId() = 0;
}

例如Circle班级想要:

struct Circle {
    // interface
    bool hitTest(Point p) override;
    Rect boundingRect() override;
    std::string uniqueId() override;
    
    double radius() const;
    Point center() const;
    // other stuff
}

我想用std::variant<Circle, Image, Polygon>将我的类的实例存储在std::vector然后像这样使用它:

using VisualElement = std::variant<Circle, Image, Polygon>;

std::vector<VisualElement> elements;
VisualElement circle = MakeCircle(5, 10);
VisualElement image = MakeImage("path_to_image.png");

elements.push_back(circle);
elements.push_back(image);
auto const &firstElement  = elements[0];
std::cout << firstElement.uniqueId() << std::endl;

使用继承,我可以通过创建一个基类来做到这一点,然后我的每个类都将成为基类的子类(显然,如果派生类没有实现该程序将无法编译的接口)。然后,我可以使用智能指针将实例存储在向量中,而不是使用变体(例如std::vector<std::unique_ptr<BaseElement>>)。我想避免这种情况,所以我想知道使用以下命令强制执行相同设计的最佳方法(如果有的话)是什么std::variant和 C++20。


首先要做的是定义一个concept确保给定类型具有您想要的接口。有很多方法可以做到这一点,例如,您可以让类型继承自BaseElement,然后写:

template<typename T>
concept VisualElementInterface = std::is_base_of_v<BaseElement, T>;

如果您不想使用继承,您可以编写自己的检查T具有所需的接口。

创建概念后,您可以为std::variant将其所持有的允许类型限制为满足该概念的类型:

template<VisualElementInterface... Ts>
using VisualElementVariant = std::variant<Ts...>;

然后你可以声明:

using VisualElement = VisualElementVariant<Circle, Image, Polygon>;

请注意,您不能使用.uniqueId() on a std::variant。但是,您可以编写一个自由函数来获取 a 中包含的元素的 IDVisualElement:

auto uniqueId(const VisualElement& element) {
    return std::visit([](auto&& el){ return el.uniqueId(); }, element);
}

并像这样使用它:

std::cout << uniqueId(firstElement) << '\n';
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 std::variant 强制使用通用接口,无需继承 的相关文章

随机推荐