如何在C++编译期强制类成员的销毁顺序?
确保C++类成员销毁顺序的编译期检查方案
先看以下C++类A的实现:
// 第三方类/方法,无法修改 class C {}; class B { public: C* getC(); }; B* legacyAPIToGetB(const char*); // 目标类 class A { public: A() { // 不使用初始化列表,因为构造B前需要先计算一些值 b = std::unique_ptr<B>(legacyAPIToGetB("calculated value")); // 如果B构造失败则抛出异常(不完成A的构造) if (!b) throw std::exception{"Cannot get B"}; c = std::unique_ptr<C>(b->getC()); if (!c) throw std::exception{"Cannot get C"}; } std::unique_ptr<B> b = nullptr; std::unique_ptr<C> c = nullptr; };
这里b是动态库句柄,c是通过该库创建的对象。当前b的声明在c之前,所以对象销毁时c会先析构,b后析构,符合预期。但如果后续重构时意外调换b和c的声明顺序,销毁顺序就会反转,可能引发难以排查的运行时崩溃。
我希望能在有人做出这种改动时让代码编译失败(最好带自定义错误提示),这是否可行?或者这种设计从一开始就存在缺陷?
我尝试过用static_assert来确保成员顺序:
static_assert( reinterpret_cast<void*>(&reinterpret_cast<A*>(nullptr)->b) < reinterpret_cast<void*>(&reinterpret_cast<A*>(nullptr)->c), "destruction of b should be after c");
但这不符合C++标准,只能在MSVC编译器中运行。
更新1:使用offsetof宏检查成员布局
可以用offsetof宏来检查成员的布局顺序,虽然C标准规定:若类型非标准布局类型(C11起),offsetof的结果在C17前未定义,C17起为条件支持,但主流编译器在这个场景下都能正常工作。
更新2:关于成员地址顺序的标准约束
遗憾的是,直到C23才明确保证后声明的成员拥有更高的内存地址。在C23之前的标准中:
不同访问控制的成员的分配顺序未指定(编译器可能会将它们分组存放)。
另一种方案:自定义析构函数控制销毁顺序
如评论所述,可以通过自定义析构函数手动按正确顺序重置指针:
A::~A() { // 确保c先销毁,b后销毁 c.reset(); b.reset(); }
但这里存在一个问题:如果构造函数抛出异常,对象的析构函数不会执行。而正如前文所述,构造函数出错时本来就会抛出异常,这也是使用unique_ptr的原因之一。虽然可以通过一些技巧(比如先在本地创建智能指针再swap到类成员中)实现异常安全,但会增加代码复杂度。
内容的提问来源于stack exchange,提问作者AlexGuo1998
相关产品推荐
相关产品推荐

