You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TimeStampQuery类拷贝/赋值构造函数失效问题排查

TimeStampQuery类拷贝/移动逻辑问题排查与修复

问题现象

  1. MoveTest单元测试无法正常完成,陷入死循环;
  2. 在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};
};

核心问题分析

  1. MoveTest死循环原因

    • 类声明缺失_total、_done、_remaining统计成员,CallRunner和WaitForFinish中使用未初始化变量,导致循环条件永远不满足,无法退出。
    • 移动构造函数仅转移了_tsCallback,未初始化_threadCall及统计成员,移动后的对象无有效工作线程,无法处理任务。
    • 移动赋值运算符错误调用拷贝构造(TimeStampQuery(other)匹配const TimeStampQuery&),未实现真正的资源转移。
  2. 拷贝赋值后_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:30:54