1. Vấn đề nó giải quyết
std::move(source) tự nó không chuyển dữ liệu sang đâu cả. Nó biến biểu thức thành xvalue, cho phép bước chọn hàm phù hợp (overload resolution) chọn hàm dựng hoặc phép gán di chuyển nếu có.
2. Kiến thức cần có
Biết hàm dựng sao chép
T(const T&)và di chuyểnT(T&&).Biết rằng biến có tên là lvalue, kể cả khi kiểu khai báo là
T&&.
3. Ý tưởng cốt lõi
std::move là phép chuyển kiểu sang tham chiếu rvalue và giữ tính const. Thao tác nhận kết quả mới quyết định có chuyển tài nguyên hay không.
Với std::string destination = std::move(source), hàm dựng di chuyển của std::string được chọn. Với const std::string source, kết quả là const std::string&&: nó không khớp std::string&&, nên phép khởi tạo thông thường sẽ sao chép.
4. Cú pháp tối thiểu
std::string source = "resource";
auto&& alias = std::move(source);
std::string destination = std::move(source);Cần <string> và <utility>. Dòng tạo alias chỉ gắn tham chiếu tới source; dòng tạo destination mới gọi hàm dựng di chuyển.
5. Cách nó hoạt động
std::move(source)tạo một xvalue vẫn chỉ tới đối tượngsource.Trình biên dịch chọn hàm phù hợp từ các hàm dựng/phép gán đang có. Có thể chọn di chuyển, sao chép hoặc báo lỗi nếu không có hàm dùng được.
Hàm đã chọn xử lý tài nguyên. Di chuyển một số nguyên thường chỉ sao chép giá trị; không có vùng nhớ sở hữu để chuyển.
Sau di chuyển, đối tượng nguồn vẫn tồn tại. Với kiểu thư viện chuẩn như
std::string, trạng thái thường hợp lệ nhưng giá trị không được chỉ định, trừ khi API bảo đảm cụ thể hơn. Kiểu tự viết phải có quy ước riêng. Xem quy tắc đối tượng thư viện sau di chuyển.
6. Lỗi thường gặp
Giả định
source.empty()luôn đúng sau khi di chuyển chuỗi.Gọi
source.front()mà chưa kiểm tra chuỗi còn phần tử.Cho rằng
std::movebỏconst, luôn nhanh hơn sao chép hoặc luôn gọi được hàm di chuyển.Thêm
std::movevàoreturn local;theo thói quen; điều này có thể ngăn NRVO, tức tạo trực tiếp kết quả trả về từ biến cục bộ.
7. Khi nào nên dùng
Dùng std::move khi bạn cho phép thao tác tiếp theo lấy tài nguyên của đối tượng và không còn cần giá trị cũ của nó. Với tham số forwarding reference trong template cần giữ nhóm giá trị của đối số, dùng std::forward<T> theo quy tắc forwarding.
8. Ví dụ đơn giản
Mã mẫu C++20 tạo chuỗi đích rồi in nó:
std::string source = "resource";
std::string destination = std::move(source);
std::cout << destination << '\n';Kết quả là resource. Ví dụ không khẳng định nội dung của source sau đó. Bạn có thể gán source = "new value" để dùng lại nó.
Mã mẫu hoàn chỉnh
Tệp mã nguồn
dailycppinterview/058_move-move/main.cpp
// Real-World C++ Interviews Q058: What does std::move move?
// Key: `std::move` moves nothing by itself; it casts its argument to an xvalue so overload
// resolution may select a move operation. The selected operation determines what is transferred
// and the source's resulting state. Unless otherwise specified, moved-from standard-library
// objects are valid but have an unspecified state; user-defined types follow their own
// move-operation contract.
#include <iostream>
#include <string>
#include <utility>
int main() {
std::string source = "resource";
std::string destination = std::move(source);
std::cout << destination << '\n';
}
9. Điều cần nhớ
Tách hai bước: std::move đổi cách biểu thức được xét; hàm dựng hoặc phép gán được chọn mới thực hiện công việc. Đọc quy ước của kiểu để biết có thể làm gì với nguồn sau đó.
10. Câu hỏi tự kiểm tra
Đề đầy đủ: std::move thực sự di chuyển điều gì?
Vì sao auto&& alias = std::move(source) chưa chuyển nội dung chuỗi? Điều gì thay đổi nếu source là const std::string? Có thể khẳng định chuỗi nguồn rỗng sau di chuyển không?