多态值类型和接口
Polymorphic value types and interfaces
我有一个多态值类型,实现如下:
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
不需要了解所有接口类型,尽管外部用户可定义的自由函数可以代替。
由于我一直在使用多态值类型,因此我更喜欢识别这些值的两类功能:
- 所有符合条件的值类型所需的功能。 即由这个
base
多态概念类的虚拟方法强制执行的功能。 - 部分、无或所有符合条件的值类型可能提供的可选/扩展功能。
似乎您的问题更多的是关于如何处理第二类(而不是第一类)。
对于第二类,我借用了std::any
的type
成员函数和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
多态值类型的源代码,它为值类型提供了一个接口,用于约束一个或多个主体的运动。我不会说它是完美的,但它也更面向价值语义,就像我在这个答案中包含的上面的例子一样。
- 设计帮助 - 为不同类型的消息处理通用接口的设计模式
- 类具有相同的接口,但参数的类型不同
- 如何模板化堆栈分配的多态指针数组到接口,包括派生类型的相应点?
- 无法使用接口类型对priority_queue中的对象进行排序<T>
- 使用模板参数化映射旧版 C 接口的接口中的类型
- 类型不可知的抽象以使用相同的运行时接口处理正向和反向迭代器和范围?
- 使用 AWS c++ 接口将文件上传到 s3 时内容类型标签不正确
- 如何正确存储/传递接口类型的变量(现代 c++)?
- C++具有特定接口的类型进行模板专用化
- 标准::任何时 GMOCKing 接口的类型不完整
- 多态值类型和接口
- 具有依赖于实现的成员函数类型的多个静态接口
- 自定义和标准类型的数学函数的统一接口
- 如何根据接口类型运行
- C 将功能实现对象作为接口类型
- 继承和 2 种接口类型
- 如何使用Qt在Windows上检查网络接口类型是以太网或无线
- 在 C# 中加载 COM 对象会引发异常"无法将类型'System.__ComObject'的 COM 对象强制转换为接口类型...",但C++或 VB 不会
- 使用接口类型查找子窗口小部件
- 通过COM返回自定义接口类型的SAFEARRAY到VB6