C++中为何t.setData(d1)无需std::move转换为右值即可编译,而t.setData(d)却编译失败?
问题分析与解答
首先先看你提供的代码:
using namespace std; class Data { public: Data() {} ~Data() {} Data(const Data& t) { cout << "Copy Constructor" << endl; } Data(Data&& t) noexcept { cout << "Move Constructor" << endl; } Data& operator=(const Data& t) { cout << "Copy =" << endl; return *this; } Data& operator=(Data&& t) noexcept { cout << "Move =" << endl; return *this; } }; class Data2 : public Data {}; class Test { public: void setData(std::shared_ptr<Data>&& d) { data = std::forward<std::shared_ptr<Data>>(d); } private: std::shared_ptr<Data> data; }; int main() { Test t; auto d = std::make_shared<Data>(); t.setData(d); // error t.setData(move(d)); // ok auto d1 = std::make_shared<Data2>(); t.setData(d1); // why ok???????????? int debug = 0; }
这问题的核心在于std::shared_ptr的隐式转换规则和右值引用的绑定逻辑,咱们一步步拆解:
1. 为什么t.setData(d)会报错?
Test::setData的参数是std::shared_ptr<Data>&&——这是非const的右值引用。C++有个明确的规则:这种类型的引用只能绑定到右值(包括临时对象、将亡值),绝对不能直接绑定到左值。
而d是一个有名字的std::shared_ptr<Data>左值(你能对它取地址,它是一个持久存在的变量),所以直接传递给右值引用参数时,编译器会直接报错。必须用std::move(d)把它转换成右值(将亡值),才能符合参数的绑定要求,这就是move(d)能正常调用的原因。
2. 为什么t.setData(d1)可以正常编译?
这里的关键是std::shared_ptr的一个内置特性:它支持从派生类的shared_ptr隐式转换到基类的shared_ptr(因为Data2是Data的派生类,Data2*可以隐式转换为Data*,shared_ptr的模板构造函数专门支持这种向上转型)。
当你调用t.setData(d1)时,编译器会自动完成以下步骤:
- 利用
shared_ptr的模板构造函数,把左值d1(std::shared_ptr<Data2>)转换成一个临时的std::shared_ptr<Data>对象; - 这个临时对象属于右值(C++中临时对象天生就是右值);
- 而
std::shared_ptr<Data>&&参数正好可以绑定到这个右值临时对象上,所以编译完全通过,不需要额外的std::move。
补充细节
在这个隐式转换过程中,d1的引用计数会临时加1(因为临时的std::shared_ptr<Data>会和d1共享同一个Data2实例),当函数调用结束、临时对象销毁时,引用计数又会减回去,不会导致内存泄漏问题。
一句话总结
- 同类型左值
d:无转换发生,左值无法绑定到非const右值引用 → 编译报错; - 派生类
shared_ptr左值d1:触发隐式转换生成临时基类shared_ptr右值 → 可以绑定到右值引用参数 → 编译通过。
内容的提问来源于stack exchange,提问作者xiang xien
相关产品推荐
相关产品推荐

