在多个 objects 中使用地图

Use a map across multiple objects

我使用全球地图来注册一个或多个相同类型的 object。为此,我开始使用全局命名空间。

以此为例(代码未经测试,仅作为示例):

//frame.h
class frame
{
public:
  frame(int id);
  ~frame();
};

namespace frame_globals
{
  extern std::map<int, frame *> global_frameMap;
};
//frame.cpp
#include "frame.h"
namespace frame_globals
{
  std::map<int, frame *> global_frameMap;
}

frame::frame(int id)
{ 
  //[...]
  //assuming this class is exclusively used with "new"!
  frame_globals::global_frameMap[id] = this;
}

frame::~frame()
{
  frame_globals::global_frameMap.erase(id);
}

这是我使用的一个相当快速的解决方案,但现在我再次偶然发现了用例,我需要注册我的 objects,我问自己是否没有比使用全局变量(我想摆脱它)。

[编辑:似乎不正确] 静态成员变量(据我所知)没有选择,因为静态在每个 object 中都是内联的,我在所有 object 中都需要它。

最好的方法是什么?想到使用常见的 parent,但我想轻松访问我的框架 class。你会怎么解决?

或者您是否知道注册 object 的更好方法,以便我可以从其他地方获得指向它的指针?所以我可能不会完全需要 "new" 并保存 "this"?

我会考虑逆向设计。例如。使用语言内置功能有什么问题?

std::map<int, frame> frames_;

frames_.emplace(id);//adding, ie. creating frames

frames_.erase(id)//removing, ie. deleting frames

现在 creating/deleting 和以前一样简单。巧合的是,如果有人出于不同目的需要框架,这里没有问题。如果 frame 应该是多态的,你可以存储 std::unique_ptr 而不是。

另外,我会考虑让 id 成为 frame 的成员,然后存储在 std::set 而不是 std::map。

class frame
{
public:
  int id;
  frame(int id);

  friend bool operator <(const frame& lhs, const frame& rhs) {return lhs.id < rhs.id;}
};

std::set<frame> frames_;

删除只是:

frames_.erase(frame);

然而,为了根据 id 查找现在有点复杂。幸运的是手边有一个解决方案。这需要实现一个透明的比较,其中包括定义 is_transparent.

总体而言,您需要考虑所有权。问问自己:在哪里或谁会 own/store map/frame pointers/etc。把它存放在那里。在共享 所有权 的情况下,使用 shared_ptr (但仅在实际需要时使用此模式 - 这比人们使用它的情况更罕见)。


一些相关的核心指南

I.3 避免单例

R.5 and R.6 避免非常量全局变量

R.11 避免显式调用 new 和 delete

R.20 and R.21 优先选择 unique_ptr 而不是 shared_ptr 除非你需要分享所有权

理想情况下,您应该有一个工厂模式来创建删除 frame 对象。 frame 的构造函数不得管理地图。这就像公民在管理他们的护照(理想情况下,政府 公民这样做)。让经理 class 在地图 (std::map<int,frame>) 中保留 frame。管理器 class 提供的方法将创建对象:

class FrameManager
{
    std::map<int,frame> Frames;
public:
    frame CreateFrame(int id) 
    {
           frame new_frame(id);
           Frames[id] =  new_frame;
    }      
};

移除情况如何?那么,根据您的设计和要求,您可以拥有 either/both 个:

  • 通过框架的析构函数删除框架。它可能会调用 FrameManager::Remove(id);
  • 仅允许 FrameManager 删除 (FrameManager::Remove(id))。我会选择这个(详见下文)。

现在,请注意,使用这种方法,将创建许多对象 frame(本地、分配给地图、return 等)。您可以使用 shared_ptr<frame> 作为 return 类型 CreateFrame,并保持 shared_ptr 作为地图类型 map<int, shared_ptr<frame>>。 使用 shared/unique_ptr 可能看起来很复杂,但它们非常有用。您无需管理生命周期。您可以将相同的 shared_ptr<frame> 传递给多个函数,而无需多次创建 frame 对象。

修改版本:

class FrameManager
{
    std::map<int, shared_ptr<frame>> Frames;
public:
    shared_ptr<frame> CreateFrame(int id) 
    {
        if(Frames.count(id)>0) return nullptr;  // Caller can check shared_ptr state

        shared_ptr<frame> new_frame = make_shared<frame>(id);
        Frames[id] =  new_frame;

        return new_frame;
    }      
};

使用框架管理器将允许完全控制框架创建、更好的错误处理、safe/secure 代码、安全的多线程代码。

我想根据您发布的代码提出几点意见。

  1. 有些人如何知道 frame_globals::global_frameMap 以及它在不查看源代码的情况下的作用。
  2. 如果它是一个全局对象那么frame class逻辑可以被绕过并且可以从外部修改全局对象。
  3. 如果有人想以不同的方式管理 frame 对象,或者如果将来某些要求发生变化,例如如果你想允许重复的帧,那么你必须更改 frame 对象。
  4. 框架class不仅有frame相关的数据成员和成员函数,还有管理逻辑。好像没关注single responsibility principle.
  5. copy/move 构造函数呢? std::map/std::set 将影响 frame class.
  6. 的构造函数

我认为 frame 和如何管理之间需要逻辑分离 frames.I 有以下解决方案(我不知道所有用例,我的解决方案基于发布的代码)

frame class

  1. frame class 不应该有任何与框架无关的逻辑。
  2. 如果 frame 对象需要了解其他 frame 对象,则应提供 ContainerWrapper 的引用(不是直接 std::mapstd::set,这样更改就不会影响 frame)

ContainerWrapper class

  1. ContainerWrapper 应该管理所有 frame 个对象。
  2. 它可以根据需要有接口,例如try_emplace插入元素的returnsiterator(类似于std::map::try_emplace),而不是先创建frame 对象,然后尝试插入 std::map,它可以检查 id(key) 是否存在,如果 std::map 不存在,则只创建 frame 对象'没有指定 id.
  3. 的对象

关于 std::map/std::set,
std::set 如果框架对象一旦创建就不需要修改,因为 std::set 不会让你修改框架而不把它取出并重新插入它,或者你必须使用智能指针。

通过这种方式,容器对象可以在多个 frame 对象之间共享,而不必是全局对象。