You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何仅可移动对象的std::vector列表初始化失败?

问题:为何无法用列表初始化存储仅可移动对象的std::vector?

我可以通过列表(大括号)简洁地完成以下6种场景中的5种初始化:

可拷贝对象仅可移动对象
arrayYESYES
std::arrayYESYES
std::vectorYESNO

唯一无法正常工作的场景是尝试初始化存储仅可移动对象的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 12:59:59