为何std::vector<std::jthread>无法通过std::initializer_list初始化?
std::vectorstd::jthread列表初始化报错的问题分析
代码示例
可正常运行的代码
#include <thread> #include <vector> #include <iostream> int main() { std::vector<std::jthread> threads; threads.emplace_back([] { std::cout << "New thread via .emplace_back()" << std::endl; }); }
编译报错的代码
#include <thread> #include <vector> #include <iostream> int main() { std::vector<std::jthread> threads { {[] { std::cout << "New thread during initialization" << std::endl; }} }; }
编译错误信息:
error: use of deleted function 'std::jthread::jthread(const std::jthread&)'
核心疑问
- 是否存在未定义行为?根据std::vector构造函数#5的说明,可能是元素不满足EmplaceConstructible,但为何第一段代码无报错?
- 是否与constexpr相关?
- 为何会调用拷贝构造函数?这种初始化方式的初衷不就是避免不必要的拷贝构造吗?
问题原因解析
1. 列表初始化触发的构造路径问题
当使用std::vector<T> obj { {...} }的列表初始化语法时,编译器会优先匹配vector的初始化列表构造函数(C++标准中的构造函数#7),而非你预期的emplace构造(构造函数#5)。这个构造函数的执行逻辑是:
- 先将初始化列表中的每个元素构造为
T的临时对象; - 再尝试将这些临时对象拷贝或移动到vector的内存空间中。
由于std::jthread是不可拷贝的资源类,其拷贝构造函数被显式删除。而初始化列表中的临时对象会被视为const T类型,移动构造需要非const的右值引用,编译器无法调用移动构造,只能尝试调用已删除的拷贝构造,最终触发报错。
2. emplace_back正常运行的原因
emplace_back的核心逻辑是在vector已分配的内存空间中原地构造元素:它会直接把你传入的参数(这里是lambda表达式)转发给std::jthread的构造函数,全程不会产生临时对象,自然也不会涉及拷贝或移动操作,因此可以正常构造不可拷贝的jthread对象。
3. 关于EmplaceConstructible和constexpr的疑问解答
- 不存在未定义行为,问题完全源于构造函数的匹配逻辑差异。第一段代码用
emplace_back原地构造,满足EmplaceConstructible要求;第二段走初始化列表构造路径,和EmplaceConstructible无关。 - 与constexpr无关,
std::jthread的构造函数并非constexpr,但这不是报错的触发点,核心还是构造路径导致的拷贝构造调用。
可行的解决方式
如果想在vector初始化阶段直接构造jthread,可以采用以下两种方式:
- 利用C++20引入的
std::from_range构造,直接原地构造元素:
#include <thread> #include <vector> #include <iostream> #include <functional> #include <ranges> int main() { std::vector<std::jthread> threads(std::from_range, std::initializer_list<std::function<void()>>{ [] { std::cout << "New thread during initialization" << std::endl; } }); }
- 保持默认构造vector,再通过
emplace_back逐个添加元素(即第一段代码的实现方式)。
内容的提问来源于stack exchange,提问作者ocebe
相关产品推荐
相关产品推荐

