std::thread函数参数传递

std::thread参数传递

通过几个例子,记录一下std::thread的参数是怎样传递的

首先有一个Object类,定义了big five,用来做测试

cpp 复制代码
class Object
{
public:
    Object()
    {
        std::cout << "construct Object" << std::endl;
    }
    ~Object()
    {
        std::cout << "destruct Object" << std::endl;
    }

    Object(const Object& other)
    {
        std::cout << "cpoy Object" << std::endl;
    }

    Object& operator=(const Object& other)
    {
        std::cout << "cpoy assign Object" << std::endl;
    }

    Object(Object&& other)
    {
        std::cout << "move Object" << std::endl;
    }

    Object operator=(Object&& other)
    {
        std::cout << "move assign Object" << std::endl;
    }
};
cpp 复制代码
void func(Object obj)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}

首先需要记住,std::thread的构造函数,会将参数decay之后,以值的方式存储在std::tuple内部,调用时,再将这些副本以右值的方式,传递给可调用对象


// 测试1

cpp 复制代码
Object obj;
std::thread th(&func, obj);
th.join();

结果:

cpp 复制代码
construct Object
cpoy Object
move Object
destruct Object
destruct Object
destruct Object

此处首先调用Object 的构造函数,

线程的构造函数处,调用拷贝构造函数,创建obj的副本,并存储在内部tuple中,

最后将tuple中的obj对象,以右值的形式传递给函数func,而函数func只接受值类型的Object参数,所以会触发一次Object的移动构造函数

所以如果要避免调用func函数时的 拷贝/移动赋值函数的调用,那么func函数的参数必须写成引用的形式


// 测试2

func函数参数改为引用, 左值引用或右值引用, 以下以左值引用为例子

cpp 复制代码
void func(Object& obj)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}

Object obj;
std::thread th(&func, obj);
th.join();

结果很遗憾,编译报错,报错的原因是,在thread内部,会将参数以右值的形式传递给func函数,而该函数的参数类型为非const类型的左值引用,非const类型的左值引用不能绑定到右值上,所以编译报错

此处将func的函数参数改为const Object& 类型即可,或者是Object &&类型即可通过编译

但是该方式仍然会在thread的构造函数处调用一次Object的拷贝构造函数, 还是会存在一次拷贝


// 测试3

如果希望obj对象不要调用任拷贝,则可以使用std::ref std::cref的方式来传递obj对象到线程的构造函数,

cpp 复制代码
void func(const Object& obj)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}

Object obj;
std::thread th(&func, std::ref(obj));
th.join();

结果:

cpp 复制代码
construct Object
destruct Object

可以看到,没有调用任何拷贝、移动,来看看std::ref是怎么做的

cpp 复制代码
template <typename T>
class reference_wrapper {
private:
    T* ptr;           // 存储指向原始对象的指针

public:
    // 构造函数:接受 T&,存储地址
    reference_wrapper(T& ref) noexcept : ptr(std::addressof(ref)) {}

    // 拷贝/移动构造函数(默认浅拷贝,仅复制指针)
    reference_wrapper(const reference_wrapper&) = default;

    // 赋值操作符(默认)
    reference_wrapper& operator=(const reference_wrapper&) = default;

    // 获取原始引用
    operator T& () const noexcept { return *ptr; }

    // 显式获取引用
    T& get() const noexcept { return *ptr; }

    // 为了方便,还提供指向成员函数的调用等(略)
};
cpp 复制代码
template <typename T>
std::reference_wrapper<T> ref(T& t) noexcept {
    return std::reference_wrapper<T>(t);
}

template <typename T>
std::reference_wrapper<const T> cref(const T& t) noexcept {
    return std::reference_wrapper<const T>(t);
}

可以看到,std::ref是返回了一个reference_wrapper对象, 而reference_wrapper类的构造函数是对传入的对象做了取地址操作,在内部存储的是该对象的地址,reference_wrapper的拷贝构造函数仅仅只是内部指针的拷贝

thread的构造函数处,调用的是reference_wrapper的拷贝构造,并不是Object的拷贝构造,所以实现了无拷贝

所以我们也必须保证,在thread对象的生命周期内,obj对象需要一直存在


// 测试4

如果希望obj对象不要调用任拷贝,也可以使用lambda的方式

cpp 复制代码
void func(const Object& obj)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(2000));
}

Object obj;
std::thread th([&]() { func(obj); });
th.join();

结果:

cpp 复制代码
construct Object
destruct Object

这种方式我更推荐,给thread对象直接传递一个lambda函数,以引用捕获的方式,将参数传递给lambda函数即可

相关推荐
在世修行18 小时前
从零打造 C# 工业视觉检测系统(十一):Task 异步编程与实时性保障
开发语言·c#·task异步
lingran__18 小时前
C++ 高阶数据结构:AVL 树万字详解|原理、四种旋转、key‑value 实现、调试校验、set/map/红黑树 底层前置知识
数据结构·c++·二叉搜索树·平衡二叉树·avl树·红黑树前置
爱和冰阔落18 小时前
【Linux】两个毫无关系的进程怎么通信?命名管道 FIFO 从原理到 Server/Client 实战
android·linux·数据库·c++·vim
码匠许师傅1 天前
【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?
c++·面试
BUG研究员_1 天前
Runnable与LCEL
开发语言·人工智能·python
果果燕1 天前
实习笔记(一):NFS、Samba、VNC、Git、CMake、systemd、内核、交叉编译
linux·arm开发·c++·笔记·git
牛艺翔1 天前
C++基础
开发语言·c++
键盘会跳舞1 天前
C++ :容器适配器stack源码级拆解
开发语言·c++··stack·先进后出
美味蛋炒饭.1 天前
Git 版本控制(下)
开发语言·git·学习·总结·后端开发
我星期八休息1 天前
网络编程—网络层
开发语言·前端·网络·人工智能·智能路由器