C++:std::thread arguments must be invocable after conversion to rvalues

引言

最近在看《C++并发编程实战》的时,书上有一句话这么写:

这些参数会拷贝至新线程的内存空间中(同临时变量一样)。即使函数中的参数是引用的形式,拷贝操作也会执行。

这有悖linux c的Pthreads API和引用的含义,我便想试试,如果是引用,子线程究竟是和父进程共享一个对象,还是说各持一份拷贝。

由于cout并不具有线程安全性(来自陈硕),且使用多次输出运算符相当于调用多次operator<<(),可能使输出变得杂乱,我并没有使用它。方便起见,我也没有使用condition这样的同步原语。

于是我的测试代码是这么写的:

#include <bits/stdc++.h>
#include <unistd.h>

using namespace std;

void thread_func(string& data) {
  printf("thread : initial data is %s\n", data.c_str());

  data.assign("child thread's data\n");
  printf("thread : data altered\n");

  printf("thread : final data is %s\n", data.c_str());
}

int main(int argc, char *argv[]) {
  const char* base = "main thread's data";
  string data(base);
  printf("main : initial data is %s\n", data.c_str());
  thread t(thread_func, data);
//  thread t(thread_func, ref(data));

  sleep(5);

  data.compare(base) ?
  	cout << "book is wrong" << endl :
  	cout << "book is correct" << endl;
  t.join();
  return 0;
}

这段代码并不能通过编译,完整报错是:

In template: static_assert failed due to requirement ‘__is_invocable<void (*)(int &), int>::value’ “std::thread arguments must be invocable after conversion to rvalues”。

我求助了百度翻译,但它并不靠谱:

在模板中:由于要求“\u is \u invocable<void(*)(int&),int>::value’”std::thread arguments must be invocable after conversion to rvalues”,static \u assert失败

大致意思是:
在模板中:由于requirement ‘__is_invocable<void (*)(int &), int>::value’ ,static_assert失败,在转化为右值后std::thread参数必须被调用。

std::thread部分源码长这样:

thread() noexcept = default;
    template<typename _Callable, typename... _Args,
	     typename = _Require<__not_same<_Callable>>>
      explicit
      thread(_Callable&& __f, _Args&&... __args)
      {
	static_assert( __is_invocable<typename decay<_Callable>::type,
				      typename decay<_Args>::type...>::value,
	  "std::thread arguments must be invocable after conversion to rvalues"
	  );

#ifdef GTHR_ACTIVE_PROXY
	// Create a reference to pthread_create, not just the gthr weak symbol.
	auto __depend = reinterpret_cast<void(*)()>(&pthread_create);
#else
	auto __depend = nullptr;
#endif
	// A call wrapper holding tuple{DECAY_COPY(__f), DECAY_COPY(__args)...}
	using _Invoker_type = _Invoker<__decayed_tuple<_Callable, _Args...>>;

	_M_start_thread(_S_make_state<_Invoker_type>(
	      std::forward<_Callable>(__f), std::forward<_Args>(__args)...),
	    __depend);
      }

一些我知道的细节:

  • 对于构造函数,thread提供了两个版本,一个是默认的,另一个是模板,而具有自动推导参数的能力,因此std::thread允许我们使用各种的callable entity去设置线程的回调函数。此外,可变模板参数使我们能方便的控制回调函数参数的类型。
    注意这里不能使用function,毕竟function是要指定可调用对象的签名的,而模板能帮助我们推导出来,适应性更强。
  • __is_invocable是一个模板,作用应该是检测传入的参数是被调用。当不成立使,value为false,触发静态断言,停止编译,我并没有去深究这个工具模板的运作方式。
  • &&能够保持模板形参的const和左右值属性,在此处会发生一个引用折叠。
  • 用std::forward进行转发,std::forward返回T&&,如果是左值将被折叠为T&,仍然是一个左值,如果是右值将被折叠为T&&,仍然是一个右值。

解决方案

书上也提供了一种解决方案(怪我没有往下看…):使用std::ref

原因是:内部代码会将拷贝的参数以右值的方式进行传递,这是为了那些只支持移动的类型

关于这点我又觉得很好奇,去找了源码,但只是大概,并没有本质的解决这个问题

std::ref的源码:

  /// Denotes a reference should be taken to a variable.
  template<typename _Tp>
    _GLIBCXX20_CONSTEXPR
    inline reference_wrapper<_Tp>
    ref(_Tp& __t) noexcept
    { return reference_wrapper<_Tp>(__t); }

可以看到std::ref实际上是一个函数模板,它本身并不能成为引用,而是通过模板引用的第一条特殊推导规则,_Tp被推导为_Tp&,再交给reference_wrapper。

再看看reference_wrapper的源码:

template<typename _Tp>
    class reference_wrapper
#if __cplusplus <= 201703L
    // In C++20 std::reference_wrapper<T> allows T to be incomplete,
    // so checking for nested types could result in ODR violations.
    : public _Reference_wrapper_base_memfun<typename remove_cv<_Tp>::type>
#endif
    {
      _Tp* _M_data;

由于其他地方我也看不懂,就不放上来了,哈哈哈哈哈。
大致看出来,reference_wrapper用指针来实现引用,实际上,大多数编译器也是这么干的。
因此,std::ref使用于那些只能使用值传递而不能使用引用传递的地方

注意看到源码中还有一个std::decay,关于这个我又去找了cplusplus官网的解释
如下:

    If T is a function type, a function-to-pointer conversion is applied and the decay type is the same as: add_pointer<T>::type
    If T is an array type, an array-to-pointer conversion is applied and the decay type is the same as: add_pointer<remove_extent<remove_reference<T>::type>::type>::type
    Otherwise, a regular lvalue-to-rvalue conversion is applied and the decay type is the same as: remove_cv<remove_reference<T>::type>::type.

看最下面一行,对于其他类型,会应用一个从左值到右值的转换,可能说这里就是那个丢失引用性质的地方。

再看源码:

#ifdef GTHR_ACTIVE_PROXY
	// Create a reference to pthread_create, not just the gthr weak symbol.
	auto __depend = reinterpret_cast<void(*)()>(&pthread_create);
#else
	auto __depend = nullptr;
#endif
	// A call wrapper holding tuple{DECAY_COPY(__f), DECAY_COPY(__args)...}
	using _Invoker_type = _Invoker<__decayed_tuple<_Callable, _Args...>>;

	_M_start_thread(_S_make_state<_Invoker_type>(
	      std::forward<_Callable>(__f), std::forward<_Args>(__args)...),
	    __depend);
      }

可以看到这里实际上最终还是调用到pthread_create,此外还用到了一个tuple。

我们知道pthread_create参数类型是void*,我想应该是C++为了实现可变模板参数,把…中的内容放到一个tuple中,而为了照顾那些只支持右值传递的类型,tuple中放的是经过std::decay的类型,具体没有考证,这是个人猜想

我想到了这一步也没必要再去死扣源码了,我们的目的已经达成一大半了。

放弃扣这个细节,换成注释的内容后:

main : initial data is main thread's data
thread : initial data is main thread's data
thread : data altered
thread : final data is child thread's data
book is wrong

我们发现子线程中的data仍然是主线程的data,这符合了我们之前的猜想,可能是书本描述有一些问题吧。

忠告

不要暴露handler给其他线程,除非明确持有handler的线程早于持有对象的线程结束。

<think>好的,用户问的是“灵动岛是什么”,我需要先弄清楚这个术语的具体含义。首先,灵动岛听起来像是一个科技产品或功能的名字,可能跟智能手机有关。我记得苹果在iPhone 14 Pro系列中推出了一个叫做Dynamic Island的功能,中文翻译可能就是“灵动岛”。我需要确认这一。 接下来,我应该解释灵动岛的基本概念。它位于屏幕顶部的药丸形区域,原本是为了容纳前置摄像头和Face ID传感器而设计的。苹果通过软件将其转化为一个交互式的区域,显示实时活动和通知。这和之前的刘海屏设计不同,更具互动性。 然后需要详细说明它的主要功能,比如显示系统提醒(来电、Face ID验证、充电状态等),实时活动(音乐播放、计时器、导航等),以及交互操作(长按、轻触、滑动等手势)。这些功能让用户无需离开当前应用就能快速访问信息,提升使用效率。 还要提到它的设计理念,即通过软硬件结合,将原本的硬件缺陷转化为实用功能,体现了苹果的创新思维。同时,灵动岛的UI设计会根据内容变化形状和大小,保持视觉上的统一。 最后,可能补充一下适用机型,主要是iPhone 14 Pro和Pro Max,以及后续的iPhone 15系列是否支持。另外,可以提及用户和市场的反响,比如是否受到好评,有没有什么缺,比如适配问题或屏幕空间占用等。 检查是否有遗漏的信息,比如技术细节或与其他品牌的类似功能对比,但用户可能只需要基础介绍,所以保持简洁。确保使用中文,结构清晰,分说明,同时遵循系统级指令中的格式要求,数学公式部分可能用不上,但要注意其他格式规则。</think>### 灵动岛是什么? **灵动岛**(Dynamic Island)是苹果公司为iPhone 14 Pro及后续高端机型设计的一项交互式界面功能,旨在将屏幕顶部的“药丸形”挖孔区域(用于前置摄像头和Face ID传感器)转化为一个动态显示与交互的核心区域。它通过软硬件结合的方式,将原本静态的硬件缺陷转化为动态的信息中心。 --- ### 核心功能与特 1. **实时活动显示** 灵动岛可以动态展示当前后台任务的实时状态,例如: - 音乐播放(显示专辑封面、播放进度) - 通话计时或录音状态 - 导航指示、外卖配送进度 - 充电状态或设备连接提示 $$ \text{例:显示“正在播放:歌曲名 - 艺术家”} $$ 2. **交互式操作** 用户可以通过击、长按或滑动直接与灵动岛互动,无需跳转到对应App: - 轻触展开详细信息(如查看完整歌词) - 长按快速控制功能(如暂停音乐) - 滑动关闭任务 3. **系统通知整合** 将传统弹窗通知转化为更柔和的“气泡”样式,从灵动岛展开,减少对屏幕内容的遮挡。 4. **自适应UI设计** 灵动岛的形态会随内容动态变化,例如: - 来电时扩展为长条形显示通话人信息 - Face ID验证时短暂缩小为圆 --- ### 技术实现原理 - **软硬件协同**:通过OLED屏幕的局部亮度和色彩控制,隐藏挖孔区域的硬件边界。 - **动画过渡**:流畅的形变动画让视觉焦集中在信息本身,而非硬件形态。 --- ### 适配机型 目前支持灵动岛的机型包括: - iPhone 14 Pro / Pro Max - iPhone 15全系列 --- ### 评价与意义 - **创新性**:将硬件限制转化为功能亮,体现了“缺陷设计”的巧思。 - **争议**:部分用户认为其占用屏幕空间,且第三方App适配需时间完善。 - **行业影响**:推动了安卓厂商对类似交互设计的探索(如“灵动胶囊”)。 如果需要进一步了解具体操作或场景示例,可以随时提问!
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值