右值是临时对象或字面量,无名且生命周期短,普通左值引用不能绑定右值以防误用;右值引用(t&&)专用于绑定将亡值,配合移动语义实现资源接管。

右值到底指什么,为什么不能直接赋值给普通引用
右值是临时对象或字面量,比如 std::string("hello")、get_temp_string() 返回的临时对象,或者 42 这种字面量。它们没有名字、生命周期短,用完即销毁。
你写 std::string& r = std::string("hi"); 会编译失败,因为普通左值引用不能绑定到右值——这是 C++ 的安全机制,防止你误拿一个马上要销毁的对象地址。
真正能接住右值的是右值引用:std::string&& r = std::string("hi");。这个 && 不是“逻辑与”,而是类型修饰符,表示“我专门处理将亡值”。
- 函数返回局部对象(如
return std::string(...))时,返回值通常是右值,可触发移动 - 显式用
std::move(x)是把左值“标记”为可移动——它不移动,只是类型转换 -
std::move后原对象进入有效但未定义状态,别再读它的值(比如s.length()可能崩溃)
移动构造函数怎么写,和拷贝构造函数的关键区别在哪
移动构造函数签名必须是 T(T&& other),且通常要加 noexcept。不加 noexcept 会导致 std::vector 扩容时不敢调用移动,退化成拷贝。
立即学习“C++免费学习笔记(深入)”;
核心动作不是“复制字段”,而是“掏空对方,接管资源”。比如字符串类里,把 other.m_data 指针直接拿过来,再把 other.m_data 设成 nullptr,避免析构时 double-free。
class MyString {
char* m_data;
public:
MyString(MyString&& other) noexcept
: m_data(other.m_data) { // 接管指针
other.m_data = nullptr; // 归零,防析构释放
}
};
- 移动后源对象仍需满足析构安全:所有裸指针必须置
nullptr,智能指针可直接std::move(other.ptr) - 不要在移动构造里做深拷贝、分配内存——那等于没移
- 如果类里全是标准类型(
int、std::string等),编译器自动生成的移动构造通常就够用
什么时候移动自动发生,什么时候必须手动 std::move
移动是隐式发生的:函数返回局部对象、抛异常时临时对象传递、容器插入右值元素等场景,编译器自动选择移动构造/赋值。
但当你有一个具名变量(左值),又确定它后面不会再用,就得手动“申请移动权”:std::move(x)。这不是强制移动,只是告诉编译器:“我允许你把它当右值处理”。
- 常见误用:
v.push_back(std::move(s));之后还访问s—— 行为未定义 - 函数参数是值类型(
void f(std::string s))时,传右值会触发移动;传左值则先拷贝再移动(C++11 起支持) - 返回局部变量时,即使不写
std::move,也会优先触发移动(RVO/NRVO 可能进一步优化掉移动)
移动后访问原对象的典型崩溃现象和调试线索
最常见的表现是段错误(SIGSEGV)或 double free or corruption,尤其在析构或后续 size()/c_str() 调用时爆发。
根本原因是:移动构造/赋值没清空源对象的资源句柄,导致两个对象指向同一块内存,一个析构释放了,另一个再用就崩。
- 用 AddressSanitizer 编译(
-fsanitize=address)能快速定位野指针访问 - 在移动操作后加断点,检查关键指针是否为
nullptr,或资源计数是否归零 - 智能指针(
std::unique_ptr)能大幅降低这类错误——移动后自动置空,但裸指针、FILE*、socket fd 这类必须手工地清
移动不是银弹。只有管理堆内存、文件句柄、大缓冲区等昂贵资源的类,才值得花精力写移动语义。小结构体(几个 int)加移动反而可能因增加分支判断而变慢。











