对象销毁后清理非拥有指针的简化实现方案问询
双向无所有权指针关联的简化方案
针对你这种双向裸指针关联、销毁时需自动清理对方指针的场景,最简洁的方案是把关联逻辑封装成RAII风格的双向链接包装器,把绑定、解绑、析构清理的逻辑全部收拢到包装器里,让业务类无需重复编写样板代码,也不用依赖智能指针强制堆分配。
核心实现:RAII双向链接包装器
通过通用模板类封装所有关联逻辑,业务类只需声明包装器成员,剩下的生命周期管理全部自动完成:
#include <cassert> template<typename T> class BiLink { public: BiLink() = default; // 禁用拷贝:双向关联的拷贝通常无实际意义,若需拷贝可按需扩展 BiLink(const BiLink&) = delete; BiLink& operator=(const BiLink&) = delete; // 默认支持移动:移动后原对象关联自动转移,不破坏现有绑定关系 BiLink(BiLink&& other) noexcept : ptr_(other.ptr_) { if (ptr_) { auto& target_link = get_link_from_target(*ptr_); target_link.ptr_ = this; } other.ptr_ = nullptr; } BiLink& operator=(BiLink&& other) noexcept { if (this != &other) { unbind(); ptr_ = other.ptr_; if (ptr_) { auto& target_link = get_link_from_target(*ptr_); target_link.ptr_ = static_cast<typename T::LinkType*>(this); } other.ptr_ = nullptr; } return *this; } // 析构时自动解绑关联,清理对方指针 ~BiLink() { unbind(); } // 绑定目标对象,自动建立双向关联 void bind(T* target) { if (ptr_ == target) return; unbind(); if (!target) { ptr_ = nullptr; return; } ptr_ = target; auto& target_link = get_link_from_target(*target); assert(target_link.ptr_ != this && "检测到循环绑定"); target_link.unbind(); target_link.ptr_ = static_cast<typename T::LinkType*>(this); } // 手动解绑关联 void unbind() { if (ptr_) { auto& target_link = get_link_from_target(*ptr_); if (target_link.ptr_ == this) { target_link.ptr_ = nullptr; } ptr_ = nullptr; } } // 提供裸指针的访问接口 T* get() const noexcept { return ptr_; } T* operator->() const noexcept { return ptr_; } T& operator*() const noexcept { return *ptr_; } explicit operator bool() const noexcept { return ptr_ != nullptr; } private: friend T; using LinkType = BiLink<typename T::PartnerType>; T* ptr_ = nullptr; // 由业务类实现,返回目标对象中对应的BiLink成员 virtual LinkType& get_link_from_target(T& target) = 0; }; // 辅助宏:简化业务类的关联成员声明和接口实现 #define DECLARE_BILINK_MEMBER(PartnerClass, MemberName) \ using PartnerType = PartnerClass; \ friend class BiLink<PartnerClass>; \ BiLink<PartnerClass> MemberName; \ static BiLink<PartnerClass>& get_link_from_target(PartnerClass& target) { \ return target.MemberName; \ }
业务类改造示例
用包装器替换原来的裸指针,业务类无需手动编写析构、拷贝/移动逻辑:
struct bar; struct foo; struct foo { // 声明与bar的双向关联成员 DECLARE_BILINK_MEMBER(bar, bar_item) // 其他业务成员... }; struct bar { // 声明与foo的双向关联成员 DECLARE_BILINK_MEMBER(foo, foo_item) // 其他业务成员... }; int main() { foo obj1; bar obj2; // 只需调用一方的bind,自动建立双向关联 obj1.bar_item.bind(&obj2); // 验证关联有效性 assert(obj1.bar_item.get() == &obj2); assert(obj2.foo_item.get() == &obj1); // 临时对象销毁后,关联自动清空 { bar temp_obj; obj1.bar_item.bind(&temp_obj); assert(obj1.bar_item.get() == &temp_obj); } assert(!obj1.bar_item); // temp_obj销毁后,obj1的关联已自动清理 }
方案优势
- 零样板代码:业务类只需一行宏声明关联成员,无需编写析构、拷贝/移动的重复代码。
- 无需堆分配:完全使用裸指针,对象可栈上创建或由scene等容器统一管理,完美适配你的图形系统场景。
- 自动生命周期同步:任何一方销毁时,对方的关联指针会自动置空,彻底避免悬空指针。
- 灵活可控:支持手动解绑、移动语义,若需拷贝可按需扩展包装器逻辑。
图形系统场景适配
针对你提到的球体与线条关联场景,直接套用包装器即可:
struct Sphere; struct Line; struct Sphere { // 声明与Line的双向关联 DECLARE_BILINK_MEMBER(Line, linked_line) // 球体业务属性 float radius = 1.0f; }; struct Line { // 声明与Sphere的双向关联(起点+终点) DECLARE_BILINK_MEMBER(Sphere, start_sphere) DECLARE_BILINK_MEMBER(Sphere, end_sphere) // 线条业务属性 int color = 0xffffff; }; // 场景容器示例 class Scene { public: template<typename T> T& create() { objects.emplace_back(std::make_unique<T>()); return *static_cast<T*>(objects.back().get()); } template<typename T> void destroy(T& obj) { auto it = std::find_if(objects.begin(), objects.end(), [&](const auto& ptr) { return ptr.get() == &obj; }); if (it != objects.end()) { objects.erase(it); // 对象销毁时,包装器自动清理所有关联指针 } } private: std::vector<std::unique_ptr<void>> objects; }; int main() { Scene scene; auto& sphere1 = scene.create<Sphere>(); auto& sphere2 = scene.create<Sphere>(); auto& line = scene.create<Line>(); // 建立双向关联 sphere1.linked_line.bind(&line); line.start_sphere.bind(&sphere1); sphere2.linked_line.bind(&line); line.end_sphere.bind(&sphere2); // 销毁线条后,两个球体的关联自动清空 scene.destroy(line); assert(!sphere1.linked_line); assert(!sphere2.linked_line); }
内容的提问来源于stack exchange,提问作者Ahmed AEK
相关产品推荐
相关产品推荐

