多态值类型和接口

Polymorphic value types and interfaces

本文关键字:接口 类型 多态      更新时间:2023-10-16

我有一个多态值类型,实现如下:

class ShapeValue {
public:
template<class T>
ShapeValue(const T& value) {
obj = make_unique<holder<T>>(value);
}
// ... appropriate copy constructors and such
void draw() { obj->draw(); }
private:
struct base {
virtual ~base() {}
virtual void draw() = 0;
};
template<class T>
struct holder<T> : public base {
T value;
void draw() override { value.draw(); }
}
unique_ptr<base> obj;
};

如果你不熟悉这种事情,这里有一个很好的谈话。

好的,那太好了。但是现在,如果我想将基础对象强制转换为其他接口怎么办?

这是我的动力。以前,我以典型的方式定义事物,如下所示:

class Shape {
virtual void draw() = 0;
};

然后我会定义其他接口,例如:

class HasColor {
virtual Color color() = 0;
virtual void setColor(Color) = 0;
};

所以我可以定义一个形状如下:

class MyShape : public Shape, public HasColor {
void draw() override;
Color color() override;
void setColor(Color) override;
};

因此,如果我有一堆选定的形状并且我想设置它们的颜色,我可以遍历所有形状并dynamic_cast<HasColor*>。事实证明这非常方便(顺便说一下,我的实际应用程序不是绘图应用程序,但具有类似的数据)。

我是否可以为我的多态值类型执行此操作,以使我的ShapeValue接口不需要知道每个Has接口?我可以执行以下操作,这实际上不是那么糟糕,但并不理想:

HasColor* ShapeValue::toHasColor() { return obj->toHasColor(); }

一个解决方案(经过测试)是为接口提供一个基类:

class AnyInterface {
virtual ~AnyInterface() {} // make it polymorphic
};
struct HasColor : public AnyInterface {
// ... same stuff
};

因此,我们有以下内容:

vector<AnyInterface*> ShapeValue::getInterfaces() { return _obj->getInterfaces(); }

然后可以定义一个帮助程序来获取我们想要的接口:

template<class I>
I* hasInterface(Shape& shape) {
for(auto interface : shape.getInterfaces()) {
if(auto p = dynamic_cast<I*>(interface)) {
return p;
}
}
return nullptr;
}

这样ShapeValue就不需要了解所有接口类型。

接受的答案似乎可能是一个可行的解决方案,尽管我还没有测试过它,而且它似乎确实回退到引用语义。然而,多态值类型的激励因素是值语义

接下来是对更面向价值语义的替代解决方案的描述,ShapeValue不需要了解所有接口类型,尽管外部用户可定义的自由函数可以代替。

由于我一直在使用多态值类型,因此我更喜欢识别这些值的两类功能:

  1. 所有符合条件的值类型所需的功能。 即由这个base多态概念类的虚拟方法强制执行的功能。
  2. 部分、无或所有符合条件的值类型可能提供的可选/扩展功能。

似乎您的问题更多的是关于如何处理第二类(而不是第一类)。

对于第二类,我借用了std::anytype成员函数和std::any的非成员any_cast模板函数的实现。有了这两个功能概念,实现一些可选扩展功能的值类型集是开放的(就像命名空间对与类相反的添加开放),并且ShapeValue的接口不需要知道每个可选扩展。作为额外的好处,不需要使用类型多态性实现扩展功能 - 即有资格与ShapeValue构造一起使用的值类型不必具有任何类型的继承关系或虚函数。

下面是一个伪代码扩展问题代码的示例:

class ShapeValue {
public:
template<class T>
ShapeValue(const T& value) {
obj = make_unique<holder<T>>(value);
}
// ... appropriate copy constructors and such
ShapeValue& operator=(const ShapeValue& newValue) {
obj = newValue.obj? newValue.obj->clone(): nullptr;
return *this;
}
const std::type_info& type() const noexcept {
return obj? obj->type_(): typeid(void);
}
void draw() { obj->draw(); }
template <typename T>
friend auto type_cast(const ShapeValue* value) noexcept {
if (!value || value->type() != typeid(std::remove_pointer_t<T>))
return static_cast<T>(nullptr);
return static_cast<T>(value->obj->data_());
}
private:
struct base {
virtual ~base() = default;
virtual void draw() = 0;
virtual std::unique_ptr<base> clone_() const = 0;
virtual const std::type_info& type_() const noexcept = 0;
virtual const void* data_() const noexcept = 0;
};
template<class T>
struct holder final: base {
T value;
void draw() override { value.draw(); }
std::unique_ptr<base> clone_() const override {
return std::make_unique<holder>(value);
}
const std::type_info& type_() const noexcept override { return typeid(T); }
const void* data_() const noexcept override { return &value; }
};
unique_ptr<base> obj;
};
template <typename T>
inline auto type_cast(const ShapeValue& value)
{
auto tmp = type_cast<std::add_pointer_t<std::add_const_t<T>>>(&value);
if (tmp == nullptr)
throw std::bad_cast();
return *tmp;
}
struct Square {
int side_;
Color color_;
void draw();
Color color() { return color_; }
void setColor(Color value) { color_ = value; }
};
Color color(const ShapeValue& value)
{
if (value.type() == typeid(Square)) {
return type_cast<Square>(value).color();
}
throw std::invalid_argument("color not supported for value's type");
}
void setColor(ShapeValue& value, Color newColor)
{
if (value.type() == typeid(Square)) {
auto square = type_cast<Square>(value);
square.setColor(newColor);
value = square;
return;
}
throw std::invalid_argument("setColor not supported for value's type");
}

对于一个更详细、可编译、测试且typeid/std::type_info-free 示例,可以查看我刚刚完成的Joint多态值类型的源代码,它为值类型提供了一个接口,用于约束一个或多个主体的运动。我不会说它是完美的,但它也更面向价值语义,就像我在这个答案中包含的上面的例子一样。