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

移动含std::thread的MyClass实例时引用失效问题求解

问题描述
// USING C++ 17
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>

class MyClass {
    std::thread thread;
    std::string mystring;

public:
    bool done = false;

    MyClass(std::string string) {
        mystring = string;

        thread = std::thread(&MyClass::worker, std::ref(*this));
        thread.detach();
    }
    void worker() {
        // notice how mystring has the wrong value, some of the time
         std::cout << "mystring is: \"" << mystring << "\"" << std::endl;

         std::this_thread::sleep_for(std::chrono::milliseconds(1000));

         done = true;
    }
};

int main() {
    std::vector<MyClass> my_vec {};
    for (int i=0; i<15; i++) {
        my_vec.emplace_back("hello");
    }
    
    while (my_vec.size() > 0) {
        for (auto it = my_vec.begin(); it != my_vec.end();) {
            if (it->done) {
                it = my_vec.erase(it);
            } else {
                ++it;
            }
        }
        
    }

    return 0;
}

需求是将MyClass实例存储在std::vector中,定期遍历并移除已完成(done为true)的实例。但调用emplace_back时,vector扩容会触发已有实例的移动构造,导致worker线程内的this引用指向旧内存,mystring值偶尔出现错误。需要保留GUI事件循环式的遍历移除模式,已尝试重写移动构造、转移线程、静态worker传引用均未解决问题。

解决方案

方法1:用智能指针存储实例,避免vector移动对象

将vector的元素类型改为std::unique_ptr<MyClass>,vector扩容时仅移动指针,不会移动MyClass实例本身,worker线程的this引用始终指向正确的内存地址。

修改后代码示例:

// USING C++ 17
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <memory>

class MyClass {
    std::thread thread;
    std::string mystring;

public:
    bool done = false;

    MyClass(std::string string) : mystring(std::move(string)) {
        thread = std::thread(&MyClass::worker, this);
        thread.detach();
    }

    // 禁止拷贝,允许默认移动构造
    MyClass(const MyClass&) = delete;
    MyClass& operator=(const MyClass&) = delete;
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;

    void worker() {
        std::cout << "mystring is: \"" << mystring << "\"" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        done = true;
    }
};

int main() {
    std::vector<std::unique_ptr<MyClass>> my_vec{};
    for (int i = 0; i < 15; i++) {
        my_vec.emplace_back(std::make_unique<MyClass>("hello"));
    }

    while (!my_vec.empty()) {
        for (auto it = my_vec.begin(); it != my_vec.end();) {
            if ((*it)->done) {
                it = my_vec.erase(it);
            } else {
                ++it;
            }
        }
    }

    return 0;
}

说明:unique_ptr托管MyClass实例,vector存储指针,扩容时仅移动指针,不会触发MyClass的移动构造,彻底避免worker线程的引用失效问题。

方法2:提前给vector预留足够空间,避免扩容移动

若能预估需要存储的MyClass实例数量,提前用reserve分配足够内存,后续emplace_back不会触发扩容,也就不会移动已有实例。

修改main函数部分:

int main() {
    std::vector<MyClass> my_vec{};
    my_vec.reserve(15); // 提前预留15个元素的空间
    for (int i=0; i<15; i++) {
        my_vec.emplace_back("hello");
    }
    
    while (my_vec.size() > 0) {
        for (auto it = my_vec.begin(); it != my_vec.end();) {
            if (it->done) {
                it = my_vec.erase(it);
            } else {
                ++it;
            }
        }
    }

    return 0;
}

说明:仅适用于能提前确定实例数量的场景,若后续添加实例超过预留数量,仍会触发扩容移动,局限性较大。

方法3:将核心状态存入共享智能指针,分离线程绑定的状态

把mystring和done等状态放到独立结构体中,用std::shared_ptr托管,worker线程绑定该共享指针。即使MyClass实例被移动,共享指针指向的状态内存地址不变。

修改后代码示例:

// USING C++ 17
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <memory>

class MyClass {
    std::thread thread;
    struct State {
        std::string mystring;
        bool done = false;
    };
    std::shared_ptr<State> state;

public:
    MyClass(std::string string) : state(std::make_shared<State>()) {
        state->mystring = std::move(string);
        thread = std::thread(&MyClass::worker, state);
        thread.detach();
    }

    // 允许默认拷贝和移动构造
    MyClass(const MyClass&) = default;
    MyClass& operator=(const MyClass&) = default;
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;

    // 对外暴露done状态的访问接口
    bool is_done() const { return state->done; }

private:
    static void worker(std::shared_ptr<State> state) {
        std::cout << "mystring is: \"" << state->mystring << "\"" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        state->done = true;
    }
};

int main() {
    std::vector<MyClass> my_vec{};
    for (int i = 0; i < 15; i++) {
        my_vec.emplace_back("hello");
    }

    while (!my_vec.empty()) {
        for (auto it = my_vec.begin(); it != my_vec.end();) {
            if (it->is_done()) {
                it = my_vec.erase(it);
            } else {
                ++it;
            }
        }
    }

    return 0;
}

说明:线程绑定的是共享指针指向的状态,与MyClass实例的存储位置无关,无论实例如何移动,状态的内存地址始终稳定,灵活性最高,适合动态添加实例的场景。

内容的提问来源于stack exchange,提问作者eth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:02:10