TimeStampQuery类拷贝/赋值构造函数失效问题排查
TimeStampQuery类拷贝/移动逻辑问题排查与修复
问题现象
MoveTest单元测试无法正常完成,陷入死循环;- 在Pinvoke导出函数
SetTimestampCallback中,局部TimeStampQuery对象拷贝赋值给ffreader->TSquery后,后者的_tsCallback成员变为空指针,未完成预期拷贝。
代码片段
类声明部分
typedef void(__stdcall* TimeStampCallback)(FF::TimestampPack tsp); namespace FF { struct __declspec(dllexport) TimestampPack { int TimeStamp; int ReaderIndex; TimestampPack(int ts, int index); std::wstring Towstring(); }; } class __declspec(dllexport) TimeStampQuery { protected: mutable std::mutex _mtx, mtxExit; std::condition_variable _cv, _cvExit; bool _run{ true }; TimeStampCallback _tsCallback; FF::QueueStats* _lastStats{}; std::queue<FF::TimestampPack> _queries; std::thread _threadCall; public: TimeStampQuery(TimeStampCallback tscb); TimeStampQuery(const TimeStampQuery& other); TimeStampQuery(TimeStampQuery&& other) noexcept; TimeStampQuery& operator=(const TimeStampQuery& other); TimeStampQuery& operator=(TimeStampQuery&& other) noexcept; void swap(TimeStampQuery& other) noexcept; ~TimeStampQuery(); void Add(int timestamp, int index); void Add(FF::TimestampPack tsp); void CallRunner(); void WaitForFinish(); };
类实现部分
FF::TimestampPack::TimestampPack(int ts, int index) { TimeStamp = ts; ReaderIndex = index; } std::wstring FF::TimestampPack::Towstring() { std::wstringstream wstr{}; wstr << "timestamp " << TimeStamp << " from [" << ReaderIndex << "]"; return wstr.str(); } TimeStampQuery::TimeStampQuery(TimeStampCallback tscb) : _tsCallback(tscb), _threadCall([&] { CallRunner(); }) { } TimeStampQuery::TimeStampQuery(const TimeStampQuery& other): TimeStampQuery(other._tsCallback) { } TimeStampQuery::TimeStampQuery(TimeStampQuery&& other) noexcept: _tsCallback(std::exchange(other._tsCallback, nullptr)) { } TimeStampQuery& TimeStampQuery::operator=(const TimeStampQuery& other) { if (this == &other) return *this; TimeStampQuery(other).swap(*this); return *this; } TimeStampQuery& TimeStampQuery::operator=(TimeStampQuery&& other) noexcept { TimeStampQuery(other).swap(*this); return *this; } void TimeStampQuery::swap(TimeStampQuery& other) noexcept { using std::swap; swap(_queries, other._queries); } TimeStampQuery::~TimeStampQuery() { _run = false; _cv.notify_one(); _threadCall.join(); } void TimeStampQuery::Add(int timestamp, int index) { FF::TimestampPack tsp(timestamp, index); Add(tsp); } void TimeStampQuery::Add(FF::TimestampPack tsp) { std::lock_guard addLock(_mtx); _queries.push(tsp); _cv.notify_one(); } void TimeStampQuery::CallRunner() { while (true) { std::unique_lock readPopLock(_mtx); _cv.wait(readPopLock, [this] { return !_run || !_queries.empty(); }); if (!_run) break; auto q = _queries.front(); _queries.pop(); _remaining--; readPopLock.unlock(); _tsCallback(q); _done++; _cvExit.notify_one(); } } void TimeStampQuery::WaitForFinish() { while (true) { std::unique_lock exitLock(mtxExit); _cvExit.wait(exitLock, [this] { return _done == _total || _queries.empty(); }); if (_done == _total) break; } _run = false; _cv.notify_one(); }
单元测试部分
static MessageChangedCallback log = [](const wchar_t* z) { Logger::WriteMessage(z); }; static TimeStampCallback tscb = [](FF::TimestampPack tsp) { Logger::WriteMessage(tsp.Towstring().c_str()); }; TEST_CLASS(TimeStampQueryTests) { public: TEST_METHOD(AddTest) { TimeStampQuery sut(tscb); sut.Add(33, 2); sut.WaitForFinish(); Assert::AreEqual(1, sut.GetStats().Total); Assert::AreEqual(1, sut.GetStats().Done); Assert::AreEqual(0, sut.GetStats().Remaining); } TEST_METHOD(CopyTest) { TimeStampQuery sut(tscb); auto sut2 = sut; sut2.Add(34, 3); Assert::AreNotEqual((long long)&sut, (long long)&sut2); sut.WaitForFinish(); sut2.WaitForFinish(); Assert::AreEqual(1, sut2.GetStats().Total); Assert::AreEqual(1, sut2.GetStats().Done); Assert::AreEqual(0, sut2.GetStats().Remaining); } TEST_METHOD(MoveTest) // 无法完成 { TimeStampQuery sut(tscb); auto sut2 = std::move(sut); sut2.Add(34, 3); Assert::AreNotEqual((long long)&sut, (long long)&sut2); sut2.WaitForFinish(); Assert::AreEqual(1, sut2.GetStats().Total); Assert::AreEqual(1, sut2.GetStats().Done); Assert::AreEqual(0, sut2.GetStats().Remaining); } };
Pinvoke赋值失效场景
#ifndef Pinvoke #define Pinvoke extern "C" __declspec(dllexport) #endif Pinvoke auto __cdecl SetTimestampCallback(FFreader* ffreader, TimeStampCallback tscb) -> void { TimeStampQuery tsq(tscb); // tsq._tsCallback == tscb // 正常 ffreader->TSquery = tsq; // ffreader->TSquery._tsCallback == 0x0000000000000000 // 不符合预期 } // 断点位置
FFreader类定义片段:
class __declspec(dllexport) FFreader { TimeStampQuery TSquery{nullptr}; };
核心问题分析
MoveTest死循环原因
- 类声明缺失
_total、_done、_remaining统计成员,CallRunner和WaitForFinish中使用未初始化变量,导致循环条件永远不满足,无法退出。 - 移动构造函数仅转移了
_tsCallback,未初始化_threadCall及统计成员,移动后的对象无有效工作线程,无法处理任务。 - 移动赋值运算符错误调用拷贝构造(
TimeStampQuery(other)匹配const TimeStampQuery&),未实现真正的资源转移。
- 类声明缺失
拷贝赋值后_tsCallback为空原因
swap函数仅交换_queries队列,未处理_tsCallback等关键成员,目标对象保留初始的nullptr(来自FFreader的初始化)。- FFreader中
TSquery{nullptr}初始化合法,但初始_tsCallback为空,赋值时未被正确覆盖。
修复方案
1. 补全类声明缺失成员
在TimeStampQuery的protected段添加统计变量:
class __declspec(dllexport) TimeStampQuery { protected: // ... 原有成员 ... int _total{0}; int _done{0}; int _remaining{0}; public: // ... 原有成员 ... FF::QueueStats GetStats() const { return {_total, _done, _remaining}; // 假设QueueStats包含对应字段 } };
2. 修正移动构造与移动赋值
// 移动构造:转移资源并终止原对象线程 TimeStampQuery::TimeStampQuery(TimeStampQuery&& other) noexcept : _tsCallback(std::exchange(other._tsCallback, nullptr)), _run(std::exchange(other._run, false)), _queries(std::move(other._queries)), _total(other._total), _done(other._done), _remaining(other._remaining), _threadCall(std::move(other._threadCall)) { other._cv.notify_one(); if (other._threadCall.joinable()) { other._threadCall.join(); } } // 移动赋值:先终止当前线程,再移动构造临时对象交换 TimeStampQuery& TimeStampQuery::operator=(TimeStampQuery&& other) noexcept { if (this == &other) return *this; // 终止当前对象线程 _run = false; _cv.notify_one(); if (_threadCall.joinable()) { _threadCall.join(); } TimeStampQuery(std::move(other)).swap(*this); return *this; }
3. 完善swap函数
交换所有可交换的关键成员:
void TimeStampQuery::swap(TimeStampQuery& other) noexcept { using std::swap; swap(_tsCallback, other._tsCallback); swap(_run, other._run); swap(_queries, other._queries); swap(_total, other._total); swap(_done, other._done); swap(_remaining, other._remaining); // 互斥量、条件变量不可交换,各自保留实例 }
4. 修正Add方法的统计逻辑
void TimeStampQuery::Add(FF::TimestampPack tsp) { std::lock_guard addLock(_mtx); _queries.push(tsp); _total++; _remaining++; _cv.notify_one(); }
5. 可选:优化拷贝构造逻辑
若需真正的语义拷贝,确保队列和统计成员正确复制:
TimeStampQuery::TimeStampQuery(const TimeStampQuery& other) : TimeStampQuery(other._tsCallback), _total(other._total), _done(other._done), _remaining(other._remaining) { std::lock_guard<std::mutex> lock(other._mtx); _queries = other._queries; }
修复验证
MoveTest:移动后的对象拥有有效工作线程,统计成员初始化正确,WaitForFinish可正常退出;- Pinvoke场景:拷贝赋值后
ffreader->TSquery._tsCallback会正确保留传入的回调指针。
内容的提问来源于stack exchange,提问作者Soleil
相关产品推荐
相关产品推荐

