裸指针无法安全管理连接上下文,因易致悬垂、重复释放或内存泄漏;应于三次握手完成并初始化后用std::shared_ptr包裹Connection,并配合weak_ptr+is_alive()检查、原子状态标记及细粒度锁保障线程安全。

为什么不能直接用裸指针管理连接上下文
裸指针(Connection*)在即时通讯场景下极易引发悬垂、重复释放或内存泄漏——比如一个连接断开后,多个线程仍持有其指针并尝试读写 status 或调用 send(),结果触发 Segmentation fault。更隐蔽的问题是:连接对象生命周期由网络事件驱动(如 epoll_wait 返回 EPOLLIN 后创建,read() 返回 0 后销毁),而业务逻辑(如消息路由、心跳检查)可能跨线程访问同一连接,裸指针无法表达“此指针是否仍有效”这一语义。
std::shared_ptr<connection></connection> 的正确绑定时机
必须在连接完成三次握手、分配好 socket fd 并初始化完协议解析器(如 ProtobufDecoder)之后,才构造第一个 std::shared_ptr;在此之前,所有中间对象(如 AcceptHandler 临时栈变量)都不得持有该指针。否则,若 accept 失败或 TLS 握手被中断,shared_ptr 构造成功但对象处于半初始化状态,后续 use_count() 非零却不可用。
- 正确做法:在
on_connection_established(int fd)回调里 newConnection(fd),立即用std::shared_ptr包裹,并传给 reactor 的 event loop 和业务 dispatcher - 错误做法:在
accept()返回 fd 后立刻 newConnection,但尚未设置 socket 选项(如SO_KEEPALIVE)或启用非阻塞模式,此时指针已泄露到其他模块 - 注意:
Connection析构函数中必须显式关闭fd,且不能依赖shared_ptr的销毁顺序——因为Connection可能持有std::shared_ptr<sessionmanager></sessionmanager>,形成循环引用
如何避免 weak_ptr 检查失效导致的竞态
当工作线程需要异步处理某连接的消息(如反垃圾过滤),常通过 std::weak_ptr<connection></connection> 持有弱引用,避免延长生命周期。但仅调用 lock() 不够——若 lock() 成功返回非空 shared_ptr,不代表连接仍处于活跃状态:Connection::state 可能已是 CLOSED,或正在被主线程执行 shutdown()。
- 每次从
weak_ptr::lock()获取shared_ptr后,必须检查conn->is_alive() && conn->get_fd() >= 0 - 不要在
lock()后缓存conn->get_fd()到局部变量,因为 fd 可能在下次系统调用前被close()且重用——需每次操作前重新验证 - 对关键操作(如发送响应),建议用
std::atomic<bool> Connection::sending_allowed</bool>配合 CAS,比单纯查 state 更及时反映连接真实可用性
多线程环境下 shared_ptr 的原子操作陷阱
std::shared_ptr 的拷贝和析构本身是线程安全的,但其指向的 Connection 对象内部状态不是。常见误用是多个线程同时调用 conn->set_last_active_time(std::time(nullptr)) 而未加锁,导致时间戳错乱,进而影响超时踢出逻辑。
立即学习“C++免费学习笔记(深入)”;
- 所有非 const 成员函数,只要修改
Connection内部字段(如recv_buffer、heartbeat_count),都应加std::mutex或使用std::atomic类型(仅限简单类型) - 避免在
shared_ptr拷贝过程中持有锁——即不要在锁区内执行auto p = conn_ptr;,应先解锁再拷贝,否则会扩大临界区,拖慢 reactor 线程 - 调试时可启用
shared_ptr的调试模式(GCC 下定义_GLIBCXX_DEBUG),捕获跨线程析构或重复 reset
真正难处理的是连接上下文与业务状态的耦合:比如一个 Connection 关联着 UserSession,而后者又关联着 GroupChat。这时候光靠指针管理不够,得配合引用计数+所有权转移协议,否则容易出现“连接已关但群聊消息还在往它发”的问题。


















