代码之家  ›  专栏  ›  技术社区  ›  Troskyvs

std::线程构造函数:通过引用传递值需要调用ref(),为什么?[副本]

  •  0
  • Troskyvs  · 技术社区  · 4 年前

    (1) 我有以下代码片段:

    void tByRef(int& i) {
        ++i;
    }
    
    int main() {
        int i = 0;
        tByRef(i); // ok
        thread t1(tByRef, i); // fail to compile
        thread t2(tByRef, (int&)i); // fail to compile
        thread t3(tByRef, ref(i)); // ok
        return 0;
    }
    

    正如你所看到的,函数 tByRef 接受左值引用作为参数以更改 i 价值所以直接调用它 tByRef(i) 通过编译。

    但当我尝试为线程函数调用做同样的事情时,例如。 thread t1(tByRef, i) ,无法编译。只有当我添加 ref() 围绕 我 ,然后进行编译。

    为什么需要额外 ref 在这里打电话?如果这是通过引用传递所必需的,那么如何解释 tByRef(i) 被编译?

    (2) 然后我将tByRef改为 template 函数 && 参数,这次,甚至 t3 未能编译:

    template<typename T>
    void tByRef(T&& i) {
        ++i;
    }
    

    这 && 在模板中,参数类型被称为引用塌陷,它可以接受左值和右值引用。为什么在我的示例代码中,t1、t2、t3都无法编译以匹配它?

    谢谢

    2 回复  |  直到 4 年前
        1
  •  2
  •   Nicol Bolas    4 年前

    线程从启动它们的代码异步执行。这就是重点。这意味着,当实际调用线程函数时,启动线程的代码很可能已经离开了调用堆栈。如果用户传递了对局部变量的引用,那么在调用线程函数时,该变量可能已脱离堆栈。基本上,通过引用传递线程函数是 非常 危险的

    然而,在C++中,通过引用传递变量是微不足道的;您只需为通过引用获取其参数的函数提供名称。由于在这种特殊情况下是如此危险, std::thread 采取措施阻止你这么做。

    当 thread 对象,并且线程函数的参数从这些副本初始化。

    现在 线 无法初始化非- const lvalue引用参数,并引用该参数的内部对象。然而,一个函数 明确地 采取非- const 左值引用几乎总是一个期望以其他人可见的方式修改该值的函数。但是它对任何人都不可见,因为它将被赋予对内部存储在 线 可访问的 没有其他人 。

    简而言之,无论你认为会发生什么,都不会发生。因此出现编译错误: 线 是专门为检测这种情况并假设你犯了某种错误而设计的。

    然而,尽管不是- const 左值参考参数本质上是危险的,但它们仍然有用。所以 std::ref 用作用户 明确地 要求传递一个参考参数。


    至于为什么在第二个例子中编译失败, tByRef 在这种情况下,不是函数的名称。它是一个 样板 。 std::螺纹 期望被赋予一个它可以调用的值。模板不是值,也不能转换为值。

    函数模板是指当提供模板参数时生成函数的构造。模板名称 单独地 不是函数。

        2
  •  -1
  •   Marco Maia    4 年前

    1)

    因为您需要一个对象传递给另一个线程,因此您需要用一个模仿引用但不是真正引用的对象来封装变量。

    std::ref docs.

    注意 possible implementation 中的部分 std::reference_wrapper 文档中,它保存一个指向对象的指针。

    2)

    C++需要一种方法来推导模板类型,编译器为每个不同的模板创建一个新函数。在这种情况下,必须指定类型:

    std::thread t3(tByRef<std::reference_wrapper<int>>, std::ref(i));