为何仅可移动对象的std::vector列表初始化失败?
问题:为何无法用列表初始化存储仅可移动对象的std::vector?
我可以通过列表(大括号)简洁地完成以下6种场景中的5种初始化:
| 可拷贝对象 | 仅可移动对象 | |
|---|---|---|
| array | YES | YES |
| std::array | YES | YES |
| std::vector | YES | NO |
唯一无法正常工作的场景是尝试初始化存储仅可移动对象的std::vector,这会编译失败,报错信息如:error: call to implicitly-deleted copy constructor of 'std::unique_ptr'。
请问这是为什么?是否存在适用于该场景的替代初始化语法?
复现问题的示例程序
/* clang++ -std=c++14 -W -Wall -Werror question.cc -o question clang++ -std=c++17 -W -Wall -Werror question.cc -o question clang++ -std=c++20 -W -Wall -Werror question.cc -o question clang++ -std=c++2b -W -Wall -Werror question.cc -o question g++ -std=c++14 -W -Wall -Werror question.cc -o question g++ -std=c++17 -W -Wall -Werror question.cc -o question g++ -std=c++20 -W -Wall -Werror question.cc -o question g++ -std=c++2b -W -Wall -Werror question.cc -o question */ #include <array> #include <memory> #include <vector> int main(int, char **) { { // 可以用列表初始化存储可拷贝对象的array、std::array或std::vector std::shared_ptr<int> a[] = {std::make_shared<int>(42)}; std::shared_ptr<int> b[] {std::make_shared<int>(42)}; std::array<std::shared_ptr<int>, 1> c = {std::make_shared<int>(42)}; std::array<std::shared_ptr<int>, 1> d {std::make_shared<int>(42)}; std::vector<std::shared_ptr<int>> e = {std::make_shared<int>(42)}; std::vector<std::shared_ptr<int>> f {std::make_shared<int>(42)}; } { // 也可以用列表初始化存储仅可移动对象的array或std::array,但std::vector不行 std::unique_ptr<int> a[] = {std::make_unique<int>(42)}; std::unique_ptr<int> b[] {std::make_unique<int>(42)}; std::array<std::unique_ptr<int>, 1> c = {std::make_unique<int>(42)}; std::array<std::unique_ptr<int>, 1> d {std::make_unique<int>(42)}; // 尝试用列表初始化存储仅可移动对象的std::vector会编译失败 // clang++报错:"error: call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'" // g++ (C++14/17)报错:"error: static assertion failed: result type must be constructible from input type" // g++ (C++20/2b)报错:"error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’ #if 1 // 设为1触发错误,设为0则编译成功 std::vector<std::unique_ptr<int>> e = {std::make_unique<int>(42)}; std::vector<std::unique_ptr<int>> f {std::make_unique<int>(42)}; #endif // 目前找到的最简洁的 workaround(但无法用于const vector) // 注意:空的初始化列表可以正常初始化vector std::vector<std::unique_ptr<int>> e_workaround = {}; e_workaround.push_back(std::make_unique<int>(42)); std::vector<std::unique_ptr<int>> f_workaround {}; f_workaround.push_back(std::make_unique<int>(42)); } return 0; }
原因分析
问题的核心在于**std::initializer_list的设计限制**:
std::initializer_list中的元素是const修饰的,这意味着当std::vector通过列表初始化构造时,只能尝试调用元素的拷贝构造函数——因为移动构造需要修改源对象(将其置为有效但未定义的状态),而const对象不允许被修改。- 原生array和
std::array的列表初始化属于聚合初始化:直接在目标内存位置构造元素,不需要先将元素存入std::initializer_list再拷贝/移动,因此可以直接调用仅可移动对象的构造函数,避开了拷贝的问题。 - 而
std::vector的列表初始化构造函数依赖于std::initializer_list,所以当元素是仅可移动类型(比如std::unique_ptr)时,拷贝构造函数被删除,自然会编译失败。
替代初始化方案
1. 直接emplace_back构造(非const vector适用)
这是最直观的方案,也可以写得很紧凑:
std::vector<std::unique_ptr<int>> vec; vec.emplace_back(std::make_unique<int>(42)); vec.emplace_back(std::make_unique<int>(100));
2. 利用移动迭代器构造(C++11及以上)
借助临时数组或std::array,配合std::make_move_iterator将元素移动到vector中:
std::array<std::unique_ptr<int>, 2> temp = { std::make_unique<int>(42), std::make_unique<int>(100) }; std::vector<std::unique_ptr<int>> vec( std::make_move_iterator(temp.begin()), std::make_move_iterator(temp.end()) );
std::make_move_iterator会把迭代器的解引用结果转为右值引用,让vector调用移动构造函数转移元素所有权,而非拷贝。
3. C++20:使用from_range构造
C++20新增了std::vector的from_range构造函数,可直接从可移动范围构造,无需手动处理移动迭代器:
std::array<std::unique_ptr<int>, 2> temp = { std::make_unique<int>(42), std::make_unique<int>(100) }; std::vector<std::unique_ptr<int>> vec(std::from_range, temp);
4. 插入时手动移动元素(C++11及以上)
如果想保留类似列表的写法,可以结合insert和std::move:
std::vector<std::unique_ptr<int>> vec; vec.insert(vec.end(), { std::move(std::make_unique<int>(42)), std::move(std::make_unique<int>(100)) });
必须用std::move将临时对象转为右值,否则仍会触发拷贝。
5. Lambda初始化const vector(C++11及以上)
如果需要构造const的vector,可以用lambda创建临时vector并移动返回:
const std::vector<std::unique_ptr<int>> vec = []{ std::vector<std::unique_ptr<int>> temp; temp.emplace_back(std::make_unique<int>(42)); temp.emplace_back(std::make_unique<int>(100)); return temp; }();
临时对象会被自动移动,无需额外拷贝,最终得到const的vector。
内容的提问来源于stack exchange,提问作者Don Hatch
相关产品推荐
相关产品推荐

