将 shared_ptr 用于唯一所有权(某种程度上)——这是好的做法吗?
Using shared_ptr for unique ownership (kind of) - is this good practice?
这很难解释,但我会尽力而为。所以,我有一个 RenderComponent、EventManager 和 RenderSystem。在我的 RenderComponents 构造函数中,我引发了 RenderSystem 订阅的 renderComponentCreated 事件。使用事件参数对象,我将 RenderNode 作为数据传递,其中包含渲染系统绘制对象所需的信息(可绘制对象、位置和类型)。
到目前为止一切顺利。现在,当 renderComponent 被删除时,我希望 RenderNode 自动从 RenderSystem 中删除,同时仍然保留手动删除它的选项,例如作为对某些事件的反应。这可以使用 RenderSystem 订阅的 RenderComponentRemoveNodeEvent 来完成。
现在 'problem'。根据我的理解(以及我想要的),renderNode 应该是 RenderComponent 唯一拥有的东西(因此 unique_ptr)。但是,这将需要我复制(并为 renderNode 实现比较运算符 --> 以便在我想删除它时能够找到它)或将引用/原始指针传递给 renderNode。但是,(如果我是正确的)没有办法知道引用是否仍然引用有效对象,这意味着无法实现自动删除。
我的解决方案是共享 RenderNode(由 RenderComponent 唯一拥有)并将弱指针传递给事件。 RenderSystem 现在还维护一个弱指针列表,它检查它们是否仍指向有效对象,如果不是,则自动删除它们。所以基本上我想要的是从一个独特的指针创建一个弱指针。然而,现在的情况是,有人可以从弱指针创建一个共享指针,并让 RenderNode 存活的时间超过它应有的时间。由于托管对象本身 (RenderNode) 包含对其他对象的引用,这些对象的存在时间不会比 RenderComponent 长,这可能会导致严重的问题。
我现在的问题:这可以被认为是好的设计还是我错过了什么?
PS:抱歉,如果这个解释读起来有点笨拙(英语不是我的母语),感谢您的帮助!
使用 std::weak_ptr
授予对可能被销毁的对象的访问权限当然没有错,这就是它的发明目的。但它确实需要对象本身由 std::shared_ptr
持有。这不仅掩盖了您让对象生命周期由其父对象控制的意图,它还强制对象的动态分配并阻止它成为父对象的成员变量。
另一种方法是通过句柄跟踪指针,并有一个句柄管理器来跟踪对象是活的还是死的。实现这一点的最安全方法是使管理器成为您正在跟踪的对象的基础 class,这样 RAII 可确保它始终是最新的。这是该概念的示例实现。注:未经测试。
template<class Derived>
class HandleBased
{
public:
typedef uint64_t handle_t;
HandleBased() : m_Handle(NextHandle())
{
Map()[m_Handle] = this;
}
~HandleBased()
{
auto it = Map().find(m_Handle);
Map().erase(it);
}
handle_t ThisHandle()
{
return m_Handle;
}
static Derived* FindPtr(handle_t h)
{
auto it = Map().find(h);
if (it == Map().end())
return null_ptr;
return static_cast<Derived*>(it->second);
}
private:
static handle_t NextHandle()
{
static handle_t next = 0;
return next++;
}
static std::unordered_map<handle_t, HandleBased*>& Map()
{
static std::unordered_map<handle_t, HandleBased*> the_map;
return the_map;
}
handle_t m_Handle;
};
下面是您如何使用它的示例:
class RenderNode : public HandleBased<RenderNode>
{
};
class RenderComponent
{
std::unique_ptr<RenderNode> node1;
RenderNode node2;
public:
void Setup(RenderSystem& rs)
{
node1 = new RenderNode;
rs.nodes.push_back(node1->ThisHandle());
rs.nodes.push_back(node2.ThisHandle());
}
};
class RenderSystem
{
public:
std::list<RenderNode::Handle> nodes;
void DoRender()
{
for (auto it = nodes.begin(); it != nodes.end(); )
{
RenderNode* p = RenderNode::FindPtr(*it);
if (p == NULL)
it = nodes.erase(it);
else
{
p->DoSomething();
++it;
}
}
}
};
这很难解释,但我会尽力而为。所以,我有一个 RenderComponent、EventManager 和 RenderSystem。在我的 RenderComponents 构造函数中,我引发了 RenderSystem 订阅的 renderComponentCreated 事件。使用事件参数对象,我将 RenderNode 作为数据传递,其中包含渲染系统绘制对象所需的信息(可绘制对象、位置和类型)。
到目前为止一切顺利。现在,当 renderComponent 被删除时,我希望 RenderNode 自动从 RenderSystem 中删除,同时仍然保留手动删除它的选项,例如作为对某些事件的反应。这可以使用 RenderSystem 订阅的 RenderComponentRemoveNodeEvent 来完成。
现在 'problem'。根据我的理解(以及我想要的),renderNode 应该是 RenderComponent 唯一拥有的东西(因此 unique_ptr)。但是,这将需要我复制(并为 renderNode 实现比较运算符 --> 以便在我想删除它时能够找到它)或将引用/原始指针传递给 renderNode。但是,(如果我是正确的)没有办法知道引用是否仍然引用有效对象,这意味着无法实现自动删除。
我的解决方案是共享 RenderNode(由 RenderComponent 唯一拥有)并将弱指针传递给事件。 RenderSystem 现在还维护一个弱指针列表,它检查它们是否仍指向有效对象,如果不是,则自动删除它们。所以基本上我想要的是从一个独特的指针创建一个弱指针。然而,现在的情况是,有人可以从弱指针创建一个共享指针,并让 RenderNode 存活的时间超过它应有的时间。由于托管对象本身 (RenderNode) 包含对其他对象的引用,这些对象的存在时间不会比 RenderComponent 长,这可能会导致严重的问题。
我现在的问题:这可以被认为是好的设计还是我错过了什么?
PS:抱歉,如果这个解释读起来有点笨拙(英语不是我的母语),感谢您的帮助!
使用 std::weak_ptr
授予对可能被销毁的对象的访问权限当然没有错,这就是它的发明目的。但它确实需要对象本身由 std::shared_ptr
持有。这不仅掩盖了您让对象生命周期由其父对象控制的意图,它还强制对象的动态分配并阻止它成为父对象的成员变量。
另一种方法是通过句柄跟踪指针,并有一个句柄管理器来跟踪对象是活的还是死的。实现这一点的最安全方法是使管理器成为您正在跟踪的对象的基础 class,这样 RAII 可确保它始终是最新的。这是该概念的示例实现。注:未经测试。
template<class Derived>
class HandleBased
{
public:
typedef uint64_t handle_t;
HandleBased() : m_Handle(NextHandle())
{
Map()[m_Handle] = this;
}
~HandleBased()
{
auto it = Map().find(m_Handle);
Map().erase(it);
}
handle_t ThisHandle()
{
return m_Handle;
}
static Derived* FindPtr(handle_t h)
{
auto it = Map().find(h);
if (it == Map().end())
return null_ptr;
return static_cast<Derived*>(it->second);
}
private:
static handle_t NextHandle()
{
static handle_t next = 0;
return next++;
}
static std::unordered_map<handle_t, HandleBased*>& Map()
{
static std::unordered_map<handle_t, HandleBased*> the_map;
return the_map;
}
handle_t m_Handle;
};
下面是您如何使用它的示例:
class RenderNode : public HandleBased<RenderNode>
{
};
class RenderComponent
{
std::unique_ptr<RenderNode> node1;
RenderNode node2;
public:
void Setup(RenderSystem& rs)
{
node1 = new RenderNode;
rs.nodes.push_back(node1->ThisHandle());
rs.nodes.push_back(node2.ThisHandle());
}
};
class RenderSystem
{
public:
std::list<RenderNode::Handle> nodes;
void DoRender()
{
for (auto it = nodes.begin(); it != nodes.end(); )
{
RenderNode* p = RenderNode::FindPtr(*it);
if (p == NULL)
it = nodes.erase(it);
else
{
p->DoSomething();
++it;
}
}
}
};