为何vector<unique_ptr>列表初始化用std::move编译失败,push_back则正常?
问题现象
在使用std::vector<std::unique_ptr<A>>时,列表初始化中使用std::move会编译失败,但调用push_back(std::move(a))却能正常运行,示例代码如下:
#include <memory> #include <vector> class A{ }; void func(A &¶m1){ } int main() { auto a = std::unique_ptr<A>(new A()); auto vec = std::vector<std::unique_ptr<A>>{std::move(a)}; // 编译失败 // std::vector<std::unique_ptr<A>> vec; // 正常运行 // vec.push_back(std::move(a)); return 0; }
自C++20起,push_back被定义为constexpr:
constexpr void push_back( T&& value );
疑问:为何std::move在两种场景下表现不一致?
原因解析
列表初始化的核心限制
用花括号{}做列表初始化时,std::vector会调用接受std::initializer_list<T>的构造函数。而std::initializer_list的本质是存储const限定的元素序列——也就是说,std::move(a)生成的std::unique_ptr<A>&&会被隐式转换为const std::unique_ptr<A>&。但
std::unique_ptr是独占所有权的智能指针,没有拷贝构造函数,仅提供接受非const右值引用的移动构造函数。用const左值引用尝试初始化std::unique_ptr,会因找不到匹配的构造函数而编译失败。push_back的重载匹配逻辑
调用vec.push_back(std::move(a))时,会精准匹配push_back的右值引用重载版本(void push_back(T&& value))。这里std::move(a)直接传递的是非const右值引用,完美适配std::unique_ptr的移动构造函数,能顺利完成所有权转移,因此编译运行都正常。C++20的constexpr不影响此差异
C++20将push_back标记为constexpr只是赋予了它编译期执行的能力,并未改变函数的参数类型和重载逻辑,因此和本次问题中的行为差异无关。
内容的提问来源于stack exchange,提问作者Ovidiu Buligan

