Transaction类对象计数异常:拷贝构造函数调用次数多于析构函数
兄弟,这问题我之前踩过坑!核心问题出在multimap::insert的行为上——它会完整拷贝你插入的Transaction对象,而如果你的计数逻辑是在**所有构造函数(包括拷贝构造)**里都做了递增操作,那自然会多算一次:你在main里创建的栈对象是第一个,插入容器时生成的副本是第二个,两个对象的构造都触发了计数加1,但只有栈上的那个会先析构(main函数结束时),容器里的副本要等到程序退出、容器销毁时才会析构,所以你看到的计数是2,析构只触发了1次。
下面给你几个不用手动递减计数的解决方案,按推荐程度排序:
1. 改用智能指针存储(最推荐)
别直接存Transaction对象,改成存std::shared_ptr<Transaction>到multimap里。这样插入容器的只是指针拷贝,不会触发Transaction的拷贝构造函数,计数只会在你创建原始对象的时候加一次,完美避免重复计数的问题。
示例代码:
#include <memory> #include <map> class Date {}; // 假设你的Date类已经定义 class Transaction { private: static std::multimap<Date, std::shared_ptr<Transaction>> records; static size_t transaction_count; Date date; // 把构造函数设为私有,用工厂方法控制创建逻辑 Transaction(Date d) : date(d) { transaction_count++; } public: // 工厂方法:创建对象并插入容器 static std::shared_ptr<Transaction> create(Date d) { auto ptr = std::make_shared<Transaction>(d); records.insert({d, ptr}); return ptr; } // 禁用拷贝构造和赋值,避免意外拷贝 Transaction(const Transaction&) = delete; Transaction& operator=(const Transaction&) = delete; ~Transaction() { transaction_count--; } // 提供获取计数的接口 static size_t get_count() { return transaction_count; } }; // 静态成员初始化 std::multimap<Date, std::shared_ptr<Transaction>> Transaction::records; size_t Transaction::transaction_count = 0; // main里的用法 int main() { auto tx = Transaction::create(Date{}); std::cout << Transaction::get_count() << std::endl; // 输出1,正确 return 0; }
这个方案不仅解决了计数问题,还清晰管理了对象的所有权,避免了不必要的对象拷贝开销。
2. 让拷贝构造函数不参与计数
只在用户显式创建对象的构造函数(比如带参数的构造、默认构造)里递增计数,拷贝构造函数里完全不碰计数逻辑。同时给对象加一个标志位,区分“用户创建的对象”和“容器里的拷贝对象”,确保只有用户创建的对象析构时才递减计数。
示例代码:
#include <map> class Date {}; class Transaction { private: static std::multimap<Date, Transaction> records; static size_t transaction_count; Date date; bool is_user_created; // 标记是否是用户显式创建的对象 public: // 用户调用的构造函数:递增计数 Transaction(Date d) : date(d), is_user_created(true) { transaction_count++; records.insert({d, *this}); } // 拷贝构造函数:不处理计数,标记为非用户创建 Transaction(const Transaction& other) : date(other.date), is_user_created(false) {} // 析构函数:只有用户创建的对象才递减计数 ~Transaction() { if (is_user_created) { transaction_count--; } } // 禁用赋值运算符 Transaction& operator=(const Transaction&) = delete; static size_t get_count() { return transaction_count; } }; // 静态成员初始化 std::multimap<Date, Transaction> Transaction::records; size_t Transaction::transaction_count = 0; int main() { Transaction tx(Date{}); std::cout << Transaction::get_count() << std::endl; // 输出1,正确 return 0; }
这个方案不需要修改容器的存储类型,适合不想重构容器结构的场景,但要注意标志位的维护,避免逻辑遗漏。
3. 用emplace代替insert,直接在容器内构造对象
multimap::emplace可以直接在容器的内存空间里构造对象,不需要拷贝外部的对象。这样你可以把构造参数直接传给emplace,在容器内部创建Transaction,只会触发一次构造函数,自然不会有重复计数的问题。
示例代码:
#include <map> class Date {}; class Transaction { private: static std::multimap<Date, Transaction> records; static size_t transaction_count; Date date; public: // 构造函数:递增计数 Transaction(Date d) : date(d) { transaction_count++; } // 禁用拷贝构造,确保只能通过emplace创建容器内的对象 Transaction(const Transaction&) = delete; Transaction& operator=(const Transaction&) = delete; ~Transaction() { transaction_count--; } // 静态方法:直接在容器内构造对象 static void create(Date d) { records.emplace(d, d); // 第一个d是键,第二个d是传给Transaction构造的参数 } static size_t get_count() { return transaction_count; } }; // 静态成员初始化 std::multimap<Date, Transaction> Transaction::records; size_t Transaction::transaction_count = 0; int main() { Transaction::create(Date{}); std::cout << Transaction::get_count() << std::endl; // 输出1,正确 return 0; }
这个方案完全避免了对象拷贝,性能最好,但需要你调整对象的创建方式,确保所有容器内的对象都通过emplace构造。
内容的提问来源于stack exchange,提问作者Cosmin Chiriac

