在运行时将属性与类实例关联 有没有一种习惯性的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();
}