是否有一种惯用的C ++方法可以将属性与一组固定的类实例动态关联?
假设我们有一个Element类。每个元素总是具有成员变量包含的某些属性。
struct Element {
unsigned atomic_protons;
float mass;
};
我们可能会将其他属性与每个元素相关联,但并非每个使用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类的方法,所有程序共享,我们不希望这些方法中断。
答案 0 :(得分:2)
所以,有两种情况。
您可以以静态方式将属性附加到程序。但是在编译之前必须知道这个属性。是的,有这样的习惯方式。它被称为专业化,派生或继承:
struct ProgramASpecificElement : Element
{
int someNewProperty;
};
第二种情况更有趣。如果要在运行时添加属性。然后你可以使用map,如下所示:
std::unordered_map<Element*, int> elementNewProperties;
Element a;
elementNewProperties[&a] = 7;
cout << "New property of a is: " << elementNewProperties[&a];
如果您不想为在地图中搜索而支付性能损失,那么您可以在Element中预测它可能具有新属性:
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"]);
当然,上面的代码没有任何必要的验证和封装 - 只是一些例子。
答案 1 :(得分:2)
我认为PiotrNycz建议的std::unordered_map<Element*, Flavor>
解决方案是将Flavor
与特定Element
相关联的完美“惯用”方式,但我想提出一个替代方案。
修复您想要在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 pattern。您使FlavoredElement
成为包装IElement
的装饰器,但也实现了IElement
接口。然后,您可以选择在运行时向元素添加flavor:
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();
}