C++悬垂指针问题:游戏引擎对象销毁后避免崩溃的替代方案
游戏引擎对象销毁后悬空指针的解决方案
问题描述
开发游戏引擎时遇到元素销毁相关问题:多个相机对象跟踪注视目标(GameObject或Actor),目标销毁后,相机代码因访问已释放对象崩溃。简化代码示例如下:
int* refToMyInt = nullptr; // 需要支持空指针状态 int* refToMyInt2 = nullptr; int* myInt = new int(2); refToMyInt = myInt; refToMyInt2 = refToMyInt; delete myInt; if(refToMyInt == nullptr && refToMyInt2 == nullptr) std::cout << "myInt Deleted!" << std::endl; // 永远不会执行
核心痛点:delete不会自动将所有引用指针置为nullptr,手动维护数十个引用不现实;weak_ptr每次访问需调用lock(),操作繁琐,求替代方案。
可行解决方案
方案1:侵入式存活标记+延迟销毁
给所有可跟踪对象(如GameObject/Actor)添加存活状态标记,销毁时仅标记为"已死亡",延迟到合适时机(如帧末尾)再释放内存,确保当前帧内所有引用可安全检查状态。
示例代码:
class TrackableObject { public: bool isAlive = true; // 标记为死亡,不立即释放 void MarkForDestroy() { isAlive = false; } }; // 全局延迟销毁队列 std::vector<TrackableObject*> destroyQueue; // 使用示例 TrackableObject* target = new TrackableObject(); TrackableObject* cameraRef = target; // 销毁时标记并加入队列 target->MarkForDestroy(); destroyQueue.push_back(target); // 帧末统一释放 for (auto obj : destroyQueue) { delete obj; } destroyQueue.clear(); // 访问前检查 if (!cameraRef->isAlive) { std::cout << "目标已销毁,跳过访问" << std::endl; }
方案2:句柄+对象池模式
不暴露原始指针,用整数句柄间接引用对象,通过对象池维护映射关系与存活状态,销毁时仅更新池内标记,无需修改所有引用。
示例代码:
#include <unordered_map> class TrackableObject {}; class ObjectPool { private: std::unordered_map<int, TrackableObject*> objects; std::unordered_map<int, bool> isAlive; int nextHandle = 0; public: int CreateObject() { int handle = nextHandle++; objects[handle] = new TrackableObject(); isAlive[handle] = true; return handle; } void DestroyObject(int handle) { if (isAlive[handle]) { delete objects[handle]; isAlive[handle] = false; } } TrackableObject* GetObject(int handle) { return (isAlive.count(handle) && isAlive[handle]) ? objects[handle] : nullptr; } }; // 使用示例 ObjectPool pool; int targetHandle = pool.CreateObject(); int cameraHandle = targetHandle; pool.DestroyObject(targetHandle); TrackableObject* target = pool.GetObject(cameraHandle); if (target == nullptr) { std::cout << "目标已销毁,跳过访问" << std::endl; }
方案3:封装简化版智能指针
基于weak_ptr封装自定义智能指针,自动处理空检查,隐藏lock()操作细节,用法接近原始指针。
示例代码:
#include <memory> template<typename T> class SafePtr { private: std::weak_ptr<T> weakPtr; public: SafePtr(std::shared_ptr<T> ptr) : weakPtr(ptr) {} // 重载->运算符,自动lock并返回指针 T* operator->() { return weakPtr.lock().get(); } // 显式检查有效性 bool IsValid() const { return !weakPtr.expired(); } }; // 使用示例 auto myInt = std::make_shared<int>(2); SafePtr<int> refToMyInt(myInt); SafePtr<int> refToMyInt2(myInt); myInt.reset(); // 销毁对象 if (!refToMyInt.IsValid() && !refToMyInt2.IsValid()) { std::cout << "myInt Deleted!" << std::endl; } // 安全访问 if (refToMyInt.IsValid()) { // 操作*refToMyInt或refToMyInt->xxx }
内容的提问来源于stack exchange,提问作者Fewnity
相关产品推荐
相关产品推荐

