pimpl模式下如何在GAME_IMPL构造时提前赋值_impl指针解决访问报错
问题核心原因
你遇到的问题本质是new表达式的执行顺序问题:原有写法中_impl(new GAME_IMPL(*this))的执行步骤为:
- 分配
GAME_IMPL所需的内存 - 在分配的内存上调用
GAME_IMPL的构造函数 - 将构造完成的对象指针赋值给
GAME::_impl
在步骤2执行时,GAME::_impl还是空指针,所以PLAYER构造时调用game.getImages()访问空指针就会崩溃。
可行解决方案
可以通过拆分内存分配、指针赋值、对象构造三个步骤,调整执行顺序解决问题,完全不需要修改原有业务逻辑,也不破坏Pimpl的封装性:
修改方案代码
1. 调整GAME构造函数的实现(放在cpp文件中,符合Pimpl的常规实现要求)
#include "game.h" // 注意必须包含GAME_IMPL的完整定义,所以这段代码要放在GAME_IMPL定义的同一cpp文件中 GAME::GAME() { // 第一步:分配GAME_IMPL的原始内存 void* raw_mem = operator new(sizeof(GAME_IMPL)); // 第二步:提前将内存地址赋值给_impl,此时_impl已经持有有效地址 _impl.reset(static_cast<GAME_IMPL*>(raw_mem)); // 第三步:调用定位new完成GAME_IMPL的构造,构造过程中可以正常访问GAME::_impl new (raw_mem) GAME_IMPL(*this); }
2. 保证GAME_IMPL的成员声明顺序
只要保证被公共接口访问的成员,声明在依赖公共接口的成员之前即可,你的原有声明顺序已经符合要求:
struct GAME_IMPL { // 先声明不依赖其他组件、会被公共接口访问的成员 IMAGES _images; // 再声明依赖GAME公共接口的成员 PLAYER _player; explicit GAME_IMPL( GAME& game ) : _player( game ) { } };
方案说明
- 该方案完全兼容原有业务代码,不需要修改
PLAYER、GAME的公共接口 - 不会破坏Pimpl的封装性,所有修改都在cpp内部,对外透明
std::unique_ptr仍然会正常管理内存生命周期,析构时会自动调用GAME_IMPL的析构函数并释放内存,没有内存泄漏风险
注意事项
- 必须严格遵守
GAME_IMPL的成员声明顺序:被GAME公共接口访问的成员必须放在依赖公共接口的成员之前,确保构造依赖成员时,被访问的成员已经完成初始化,不会出现未定义行为 - 不要在
GAME_IMPL的构造函数中调用GAME的虚函数,此时GAME本身还未完成构造,虚函数调用不会触发多态行为
内容的提问来源于stack exchange,提问作者c z
相关产品推荐
相关产品推荐

