应使用智能指针绑定所有权并配合ID系统与容器管理玩家实体,裸指针仅限短生命周期局部访问;推荐std::shared_ptr统一管理并存入vector或unordered_map,跨系统引用用PlayerID,观察者场景用std::weak_ptr,避免循环引用与悬空指针。

用裸指针管理玩家实体会直接导致崩溃
裸指针(Player*)本身不带生命周期管理,游戏里玩家频繁登录/登出、断线重连、跨服迁移时,极易出现悬空指针、重复释放、内存泄漏。你看到的“玩家突然消失”或“服务器 segfault”,八成是某处 delete p; 后又调了 p->update()。
真正能落地的做法是:**用智能指针绑定资源所有权,且必须配合容器与ID系统**。裸指针只允许出现在极短生命周期的局部访问中(比如函数参数传递),绝不长期持有。
std::shared_ptr 是起点,但不是终点
std::shared_ptr 解决了“谁该负责 delete”的问题,但它引入引用计数开销,且在循环引用场景下会泄漏(比如 Player 持有 std::shared_ptr<party></party>,而 Party 又反向存 std::shared_ptr<player></player>)。在线游戏里一个服务器常驻数万玩家,每个玩家每帧都参与几十次 shared_ptr 的原子增减,性能敏感区要警惕。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 玩家实体统一由
std::shared_ptr<player></player>管理,创建后立即存入中央容器(如std::unordered_map<playerid std::shared_ptr>></playerid>) - 所有跨系统引用(如战斗系统、副本系统)只存
PlayerID(整型),需要时通过 ID 查表获取shared_ptr;查不到说明玩家已下线,直接跳过 - 绝对避免在
Player类内部用std::shared_ptr指向其他玩家或自身——改用 raw pointer 或weak_ptr,并在使用前调用lock()检查有效性
std::weak_ptr 用于观察者模式和临时关联
当某个逻辑需要“可能持有玩家,但不延长其生命周期”时,std::weak_ptr 是唯一安全选择。典型场景:技能效果持续期间需要引用目标玩家、AI 怪物缓存最近攻击的玩家、视野系统暂存可见玩家列表。
错误写法:std::shared_ptr<player> target = ...; // 本不该延长target寿命</player>
正确写法:std::weak_ptr<player> target_ref = player_ptr;</player>,后续使用前必须检查:
if (auto p = target_ref.lock()) {
p->takeDamage(dmg);
} // lock() 返回空 shared_ptr 表示目标已销毁
注意:weak_ptr::lock() 有轻微原子开销,高频调用(如每帧多次)需评估是否值得;若只是读取 ID 或状态标志,可直接存 PlayerID 更轻量。
容器选型与 ID 映射比指针类型更重要
决定性能上限的往往不是指针类型,而是你怎么组织这数万个玩家。用 std::vector<:shared_ptr>></:shared_ptr> 按 ID 索引(ID 即 vector 下标)最快,但 ID 回收难;用 std::unordered_map<playerid std::shared_ptr>></playerid> 插删快,但哈希冲突和内存碎片影响缓存局部性。
推荐折中方案:
- 分配固定大小的
std::vector<:shared_ptr>></:shared_ptr>(例如 65536),ID 为 uint16_t,复用已释放槽位 - 维护一个
std::vector<playerid></playerid>作为空闲 ID 栈,pop_back()分配,push_back()归还 - 所有外部系统只通过
PlayerID访问,中央管理器提供get_player(PlayerID),内部做边界检查 + 是否为空的判断
这样既规避了哈希查找,又避免了裸指针悬挂风险,且 cache line 友好——玩家数据按内存连续排列,更新时 CPU 预取效率高。
真正麻烦的是跨线程访问:玩家实体不能简单加锁保护全部字段。更现实的做法是把状态拆解,网络收包、逻辑 tick、渲染同步走不同线程+无锁队列,而玩家指针只在逻辑线程内被 shared_ptr 持有,其他线程只传 ID 或快照值。



















