C++实例计数类实现验证:自动追踪实例数量是否可行?
你的实现分析:基础场景可行,但存在多个边缘场景会导致计数错误
你的代码在单线程、仅使用默认构造/析构、无拷贝/移动操作的简单场景下可以正常工作,但一旦涉及以下情况,计数就会出现偏差:
1. 未处理拷贝/移动构造(最常见的错误来源)
你的类只定义了默认构造函数,而C++编译器会自动生成默认拷贝构造函数和默认移动构造函数。当你执行以下操作时:
C c1; C c2 = c1; // 调用默认拷贝构造,不会触发你的构造函数,num不会增加 C c3 = std::move(c1); // 调用默认移动构造,同样不会触发num++
这时候c2和c3都是合法的实例,但num没有对应增加;而当这些对象析构时,~C()会执行num--,最终导致num的数值比实际现存实例数量少,甚至变成负数。
2. 线程安全问题
num++和num--不是原子操作,它们会被拆分成“读取-修改-写入”三个步骤。在多线程环境下,多个线程同时操作num会导致竞态条件,比如两个线程同时读取到同一个num值,各自加1后写回,最终num只增加了1,而实际创建了2个实例。
3. 异常安全问题(构造函数抛出异常时)
如果你的构造函数后续扩展了逻辑并可能抛出异常,比如:
C() { ++num; if (some_error) { throw std::runtime_error("error"); } }
当构造函数抛出异常时,对象并未成功创建,但num已经执行了++操作,且不会调用析构函数,导致num的数值比实际实例数量多。
4. 手动内存操作的异常/错误
- 使用
placement new创建对象后,如果忘记调用析构函数,num不会减少; - 手动调用析构函数多次(比如对同一个对象调用
~C()),会导致num被多次递减; - 用
new[]创建数组时,默认构造会正确触发num++,但如果数组销毁时使用delete而非delete[],会导致析构函数只被调用一次,num的递减次数不足。
5. 设计缺陷:get_number_objs应为静态函数
当前get_number_objs是非静态成员函数,必须通过类的实例才能调用。但获取现存实例数量的操作不需要依赖具体对象,应该定义为静态函数:
static int get_number_objs() { return num; }
这样你可以直接通过C::get_number_objs()调用,即使还没有创建任何实例。
改进方案
针对上述问题,修正后的类可以这样实现:
#include <atomic> class C { public: // 使用原子类型保证线程安全 static std::atomic<int> num; // 默认构造 C() { ++num; } // 拷贝构造:必须显式定义,保证计数增加 C(const C&) { ++num; } // 移动构造:同样需要显式定义 C(C&&) noexcept { ++num; } // 拷贝赋值:不影响实例数量,默认即可 C& operator=(const C&) = default; // 移动赋值:同理,不影响实例数量,默认即可 C& operator=(C&&) noexcept = default; // 析构函数 ~C() { --num; } // 静态函数,无需实例即可调用 static int get_number_objs() { return num.load(); } }; // 初始化原子变量 std::atomic<int> C::num = 0;
这个版本处理了拷贝/移动构造的计数问题,用std::atomic保证线程安全,同时修正了get_number_objs的设计缺陷。
内容的提问来源于stack exchange,提问作者RedRubber
相关产品推荐
相关产品推荐

