在运行时将属性与类实例关联
Associate properties with class instances at runtime
是否有一种惯用的C++方法可以将属性与一组固定的类实例动态关联?
例如,假设我们有一个Element类。每个元素总是具有成员变量所包含的某些属性。
struct Element {
unsigned atomic_protons;
float mass;
};
我们可能会将其他属性与每个Element关联,但并不是每个使用Element类的程序都对相同的属性感兴趣。也许有时我们对味道感兴趣,有时我们对颜色感兴趣,而表示这些特性的变量初始化起来可能很昂贵。也许直到运行时我们才知道我们想要什么属性。
我想到的解决方案是一组并行数组。一个数组包含实例本身,该数组的索引将每个实例与一系列"并行"数组中的项隐式关联。
// fixed set of Element instances
std::vector<Element> elements;
// dynamic properties
std::vector<Flavor> element_flavors;
std::vector<Color> element_colors;
每个特性向量都是根据需要创建的。
这个解决方案可以,但与惯用的C++一点也不相似。除了美观之外,这种安排还使从给定的Element实例中查找属性变得很尴尬。我们需要在每个Element实例中插入一个数组索引。此外,每个向量中的大小信息是冗余的。
它的优点是,如果我们对给定属性的所有值感兴趣,那么数据会被适当地排列。然而,通常情况下,我们希望走相反的方向。
以某种方式修改Element类的解决方案是可以的,只要不需要每次添加新属性时都修改该类。还假设存在用于处理所有程序共享的Element类的方法,并且我们不希望这些方法被破坏。
我认为PiotrNycz建议的std::unordered_map<Element*, Flavor>
解决方案是将Flavor
与特定Element
关联起来的一种完美的"idomatic"方式,但我想提出一种替代方案。
如果你想在Element
上执行的操作是固定的,你可以提取出一个接口:
class IElement {
public:
virtual ~IElement() {}
virtual void someOperation() = 0;
};
然后,您可以轻松地存储IElement
指针的集合(最好是智能指针),然后根据需要进行专门化。不同的专业具有不同的行为和包含不同的属性。您可以有一个工厂来决定在运行时创建哪个专业化:
std::unique_ptr<IElement>
elementFactory(unsigned protons, float mass, std::string flavor) {
if (!flavor.isEmpty()) // Create specialized Flavored Element
return std::make_unique<FlavoredElement>(protons, mass, std::move(flavor));
// Create other specializations...
return std::make_unique<Element>(protons, mass); // Create normal element
}
在你的情况下,问题是你可以很容易地获得专业化的爆发:Element
、FlavoredElement
、ColoredElement
、FlavoredColoredElement
、TexturedFlavoredElement
等…
在这种情况下适用的一种模式是Decorator模式。您使FlavoredElement
成为一个装饰器,它包装了IElement
,但也实现了IElement
接口。然后,您可以选择在运行时为元素添加一种风格:
class Element : public IElement {
private:
unsigned atomic_protons_;
float mass_;
public:
Element(unsigned protons, float mass) : atomic_protons_(protons), mass_(mass) {}
void someOperation() override { /* do normal thing Elements do... */ }
};
class FlavoredElement : public IElement {
private:
std::unique_ptr<IElement> element_;
std::string flavor_;
public:
FlavoredElement(std::unique_ptr<IElement> &&element, std::string flavor) :
element_(std::move(element)), flavor_(std::move(flavor)) {}
void someOperation() override {
// do special thing Flavored Elements do...
element_->someOperation();
}
};
class ColoredElement : public IElement {
private:
std::unique_ptr<IElement> element_;
std::string color_;
public:
ColoredElement(std::unique_ptr<IElement> &&element, std::string color) :
element_(std::move(element)), color_(std::move(color)) {}
void someOperation() override {
// do special thing Colored Elements do...
element_->someOperation();
}
};
int main() {
auto carbon = std::make_unique<Element>(6u, 12.0f);
auto polonium = std::make_unique<Element>(84u, 209.0f);
auto strawberry_polonium = std::make_unique<FlavoredElement>(std::move(polonium), "strawberry");
auto pink_strawberry_polonium = std::make_unique<ColoredElement>(std::move(strawberry_polonium), "pink");
std::vector<std::unique_ptr<IElement>> elements;
elements.push_back(std::move(carbon));
elements.push_back(std::move(pink_strawberry_polonium));
for (auto& element : elements)
element->someOperation();
}
因此,有两种情况。
您可以以静态方式将属性附加到程序。但是在编译之前必须知道这个属性。是的,有一种惯用的方法可以做到这一点。它被称为专门化、派生或继承:
struct ProgramASpecificElement : Element
{
int someNewProperty;
};
第二个案例更有趣。当您希望在运行时添加属性时。然后你可以使用地图,像这样:
std::unordered_map<Element*, int> elementNewProperties;
Element a;
elementNewProperties[&a] = 7;
cout << "New property of a is: " << elementNewProperties[&a];
如果你不想为在地图中搜索而支付性能损失,那么你可以在元素中预测它可能有新的属性:
struct Property {
virtual ~Property() {}
};
template <typename T>
struct SimpleProperty : Property {
T value;
};
struct Elememt {
// fixed properties, i.e. member variables
// ,,,
std::unordered_map<std::string, Property*> runtimeProperties;
};
Element a;
a.runtimeProperties["age"] = new SimpleProperty<int>{ 7 };
cout << "Age: " << *dynamic_cast<SimpleProperty<int>*>(a.runtimeProperties["age"]);
当然,上面的代码没有任何必要的验证和封装——只是几个例子。
- 从C++实例化QML
- 在全局变量中保存类的实例以重新创建类(创建"backup")
- OpenGL - 在抛出"__gnu_cxx::recursive_init_error"实例后终止调用?
- 如何在c++中为模板函数实例创建快捷方式
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 设计一个只能由特定类实例化的类(如果可能的话,通过make_unique)
- 如何创建一个空的全局类并在启动时实例化它
- 无法创建抽象类的实例
- 多个文件的内存分配错误"在抛出 'std :: bad_alloc' what (): std :: bad_alloc 的实例后终止调用" [C++]
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 我收到以下错误:抛出'std::bad_alloc'实例后终止调用
- 建议在运行时将带有类实例的列表从c++导入qml
- 关联容器的下界复杂性:成员函数与非成员函数
- 约束和显式模板实例化
- 通过实例理解std::move及其目的
- 为什么包含windows.h会产生语法错误,从而阻止类的实例化?(C2146,C2065)
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 正在生成未知类实例
- 如何从NPObject获取关联的NPP实例
- 在运行时将属性与类实例关联