在运行时将属性与类实例关联 有没有一种习惯性的C++方法来动态地将属性与固定的类实例集关联?
例如,假设我们有一个元素类。每个元素始终具有成员变量包含的某些属性在运行时将属性与类实例关联 有没有一种习惯性的C++方法来动态地将属性与固定的类实例集关联?,c++,oop,design-patterns,C++,Oop,Design Patterns,例如,假设我们有一个元素类。每个元素始终具有成员变量包含的某些属性 struct Element { unsigned atomic_protons; float mass; }; 我们可能会将其他属性与每个元素关联,但并非每个使用元素类的程序都对相同的属性感兴趣。也许有时我们对味道感兴趣,有时我们对颜色感兴趣,而表示这些属性的变量初始化起来可能代价高昂。也许直到运行时我们才知道我们需要什么属性 我想到的解决方案是一组并行阵列。一个数组包含实例本身,该数组的索引将每个实例与一系
struct Element {
unsigned atomic_protons;
float mass;
};
我们可能会将其他属性与每个元素关联,但并非每个使用元素类的程序都对相同的属性感兴趣。也许有时我们对味道感兴趣,有时我们对颜色感兴趣,而表示这些属性的变量初始化起来可能代价高昂。也许直到运行时我们才知道我们需要什么属性
我想到的解决方案是一组并行阵列。一个数组包含实例本身,该数组的索引将每个实例与一系列“并行”数组中的项隐式关联
//元素实例的固定集合
std::向量元素;
//动态特性
std::向量元素;
std::矢量元素颜色;
根据需要创建每个属性向量
此解决方案是可行的,但根本不类似于惯用的C++。除了美观之外,这种安排还使得从给定的元素实例中查找属性变得很尴尬。我们需要在每个元素实例中插入一个数组索引。此外,每个向量中的大小信息是冗余的
另外,如果我们对给定属性的所有值感兴趣,那么数据将被适当地安排。然而,通常我们想朝相反的方向走以某种方式修改元素类的解决方案是好的,只要不需要在每次添加新属性时修改该类。还假设存在用于处理所有程序共享的元素类的方法,我们不希望这些方法中断。因此,有两种情况 可以以静态方式将属性附加到程序。但是在编译之前必须知道这个属性。是的,有一种惯用的方法可以做到这一点。它被称为专门化、派生或继承:
struct ProgramASpecificElement : Element
{
int someNewProperty;
};
第二个案例更有趣。当您要在运行时添加属性时。然后您可以使用地图,如下所示:
std::unordered_map<Element*, int> elementNewProperties;
Element a;
elementNewProperties[&a] = 7;
cout << "New property of a is: " << elementNewProperties[&a];
std::无序映射元素newproperties;
要素a;
elementNewProperties[&a]=7;
我认为PiotrNycz建议的std::unordered_map
解决方案是一种完美的“idomatic”方式,可以将味道
与特定的元素
关联起来,但我想提出一种替代方案
如果要在元素上执行的操作已修复,则可以提取出一个接口:
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
}
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();
}
如果初始化它们的费用是主要问题,那么延迟初始化可能是一个选项,即携带指向成员的空指针,但只有在实际尝试访问它们时才实际创建它们。如何使用这些属性?在理想的OO设计中,属性是私有的,并且仅由元素类上的函数使用。这使生活更容易,因为您可以在元素中使用虚拟函数,然后在派生类中专门化行为(并使用附加属性)。如果开始引发派生类的爆炸,则考虑装饰器模式。@ CythRead类似于元素实例集合中的迭代,并根据每个实例以前被“标记”的属性来进行处理。因此,我们需要一种有效的方法来从元素映射到感兴趣的属性,而不必为不必要的属性付费。我不知道是否有一种合理的方法可以在运行时选择Decorator?@praxelitic,您可以通过在运行时用包装元素的Decorator替换元素来轻松地“标记”元素。装饰器和元素将从同一接口继承,IEElement说。然后,您可以拥有一个IEElement集合,并将标记的元素和未标记的元素视为相同。问题是IELENT上可用的操作是固定的。但是,您可以根据这些操作的标记方式来更改它们的行为。您有理由相信最后一个选项的性能会比以前的更好吗?您仍在搜索一个映射(当然,它可能是一个较小的映射),但同时也在执行一个不免费的动态强制转换。@ChrisDrew在现实世界的示例中,对象多于单个对象属性。当然,但在无序映射中查找是O(1)(常量)指针的散列很简单,如果有一个runtimeProperties用于多个元素实例,它可能会提前出现。它会将字符串映射到相对于每个元素指向的地址的偏移量或成员ptr。
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
}
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();
}