关于c ++:如何让对象知道它所在的容器?

How can I make an object know what container it is in?

我需要一种方法把不同的东西放在一起。每个项目一次只能在一个集合中。我还需要能够问的项目设置它是在。我有3个问题:

这是有效的实现吗?

如果我将类拆分为多个头文件,因为每个类都需要另一个头文件,那么如何使用它?

我可以在项类中使用引用而不是指向管理器的指针吗(因为我不喜欢指针)?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Manager {
public:
  add_item(Item& item) {
    items.insert(&item);
    item.manager = this;
  }
  remove_item(Item& item) {
    items.erase(&item);
    item.manager = nullptr;
  }
private:
  std::unordered_set<Item*> items;
}

class Item {
public:
  Manager* get_manager() {return manager;}
private:
Manager* manager;
}


I need a way to put various items into sets. Each item can be in only 1 set at a time. I also need to be able to ask the item which set it is in. I have 3 questions:

Is this a valid implementation?

基本的想法是合理的,尽管它不强制您的数据模型或防御有问题的场景。例如,您可以做什么:

1
2
3
4
5
6
7
void add_item(Item& item) {
    if (item.manager == this) return; // already owner
    if (item.manager) // already has another owner...
        item.manager->remove(item);
    items.insert(&item);
    item.manager = this;
}

How can I use this if I split the classes into multiple header files, because each class requires the other?

你为什么要这么做?这些类看起来太简单了,不需要任何需求,而且它只是把事情复杂化了。(正确的方法是使用前向声明头。)

Can I use a reference instead of the pointer to Manager in item class (because I don't like pointers)?

不适用于您当前的设计,因为您的代码在从Manager中删除Item后将item.Manager设置为nullptr,这是一个合理的操作。

更一般地说,您需要确保实际的Item对象的生命周期是您在Manager对象中存储指向它们的指针的时间。这可能是或不是您的代码编写方式的自然结果,也可能是在Item被破坏之前通过调用item.manager->remove(item);很容易实现的。


How can I make an object know what container it is in?

你不应该在一个适当的类设计中这样做。有些事情像依赖注入,但我认为这超出了你的实际需要。

Is this a valid implementation?

How can I use this if I split the classes into multiple header files, because each class requires the other?

使用前向声明(请查看此处了解更多详细信息)。

Can I use a reference instead of the pointer to Manager in item class (because I don't like pointers)?

不,你不能。

更深入地说,除了你实际提出的问题之外,它最终似乎是一个设计问题,而你提出的是xy问题。

你不喜欢(原始)指针是一种很好的直觉,你的实际设计可能有什么问题。不幸的是,您不能使用像std::unordered_set这样的标准容器来管理引用。

但是,您可以使用动态内存管理工具提供的智能指针。

你必须做的主要决定是,在诸如std::shared_pointerstd::weak_ptrstd::unique_ptr等各种智能指针中,哪一个是正确地管理必要的所有权要求的权利。

另外,对于您想要做的事情,Manager可能不是最佳的命名选择。如果有更好的设计,请查找经典的设计模式。

例如,听起来您需要一个类似于观察者的东西,它跟踪许多Item更改/事件,并将这些更改/事件转发给其他注册的Item实例。

由于Observer不需要对任何已注册的Item实例拥有所有权,因此std::weak_ptr似乎是引用其中任何一个实例的正确选择。


Is this a valid implementation? How can I use this if I split the
classes into multiple header files, because each class requires the
other?

您应该使用正向声明。

在一个好的面向对象设计中,您的对象不应该知道它是如何存储的。看起来您的对象不应该负责需要本地化对象的活动。

1
std::unordered_list<Item*> items;

这是不恰当的。是否要散列对象的地址?

Can I use a reference instead of the pointer to Manager in item class
(because I don't like pointers)?

传递给函数时,始终可以用引用替换指针。这没问题。


假设Manager不被期望拥有Item,并且您对ItemManager的生命周期有一个清晰的概念,那么这似乎是合理的。

如果要将类拆分为单独的头文件Item.h,只需要向前声明Manager。事实上,正如它所显示的那样,Item根本不需要Manager的完整定义,因此您可以简单地将其结构为:

项目h

1
2
3
4
5
6
7
8
9
class Manager;  // forward declaration

class Item {
public:
  Manager* get_manager() const { return manager; }
  void set_manager(Manager* new_manager) { manager = new_manager; }
private:
  Manager* manager = nullptr;  // Ensure initialized to null
};

经理H

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include"Item.h"
#include <unordered_set>

class Manager {
public:
  void add_item(Item& item) {
    if (item.get_manager())
      return;  // Item can have only one Manager
    items.insert(&item);
    item.set_manager(this);
  }
  void remove_item(Item& item) {
    items.erase(&item);
    item.set_manager(nullptr);
  }
private:
  std::unordered_set<Item*> items;
};

现场演示。

至于不喜欢指针,如果您想要一个可以为空的非拥有引用,那么指针是一个很好的匹配。指针通常比对成员变量的引用更合适,因为引用极大地限制了对类的操作。