Tham chiếu
Khai báo một biến có tên là tham chiếu, tức là bí danh của một đối tượng hoặc hàm đã tồn tại.
Tham chiếu có thể coi là con trỏ không rỗng được đóng gói trong C++, dùng để truyền đối tượng mà nó trỏ tới, và khi khai báo phải trỏ tới một đối tượng.
Tham chiếu không phải là đối tượng, nên không có mảng tham chiếu, không thể lấy con trỏ tới tham chiếu, cũng không có tham chiếu của tham chiếu.
Kiểu tham chiếu không thuộc kiểu đối tượng
Nếu muốn tham chiếu hỗ trợ sao chép/gán như đối tượng thường (ví dụ làm phần tử container), cần reference_wrapper, thường được cài bằng một con trỏ không rỗng.
Tham chiếu chủ yếu gồm hai loại: tham chiếu trái (lvalue) và tham chiếu phải (rvalue).
Lvalue và rvalue
Xem giải thích tại phân loại giá trị.
Tham chiếu trái T&
Tham chiếu thường gặp là tham chiếu trái, ràng buộc vào lvalue; tham chiếu trái có const có thể ràng buộc rvalue. Dưới đây là ví dụ từ tham khảo:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Tham chiếu trái dùng nhiều nhất ở tham số hàm để tránh copy không cần thiết.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Tham chiếu phải T&&(C++ 11)
Tham chiếu phải ràng buộc vào rvalue, dùng để di chuyển đối tượng và kéo dài vòng đời tạm.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Tham chiếu treo (dangling)
Khi đối tượng được tham chiếu bị hủy, tham chiếu trở thành dangling; truy cập là hành vi không xác định, có thể crash.
Ví dụ thường gặp:
-
Tham chiếu biến cục bộ
1 2 3 4 5 6 7 8 9 10 11
#include <iostream> int& foo() { int a = 1; return a; } int main() { int& b = foo(); std::cout << b << std::endl; // hành vi không xác định } -
Treo do giải phóng
1 2 3 4 5 6 7 8 9
#include <iostream> int main() { int* ptr = new int(10); int& ref = *ptr; delete ptr; std::cout << ref << std::endl; // hành vi không xác định } -
Treo do tái cấp phát
1 2 3 4 5 6 7 8 9 10 11
#include <iostream> int main() { std::string str = "hello"; const char& ref = str.front(); str.append("world"); // có thể tái cấp phát, ref trỏ tới bộ nhớ đã bị giải phóng std::cout << ref << std::endl; // hành vi không xác định }Các thao tác chèn của
std::vector,std::unordered_map... đều có thể gây tái cấp phát.
Khi dùng tham chiếu, luôn phải chú ý vòng đời đối tượng, tránh dangling.
Công cụ phân tích tĩnh và thói quen tốt giúp tránh lỗi này.
Mẹo tối ưu liên quan đến tham chiếu
Loại bỏ copy của đối tượng “nặng”
Các đối tượng nặng thường gặp:
- Container
vector,array,map... string- Các kiểu có copy/move constructor đặc biệt
Với đối tượng nhẹ, truyền tham chiếu không giúp gì, thậm chí tham chiếu còn tốn bộ nhớ hơn.
Điều này có thể gây overhead và cản trở tối ưu.
Đối tượng nhẹ:
- Kiểu cơ bản
int,float... - Các aggregate nhỏ
- Iterator của container chuẩn
Chuyển lvalue thành rvalue
Dùng std::move để chuyển quyền sở hữu. Thường gặp giữa biến cục bộ hoặc giữa tham số và biến cục bộ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
Không phải lúc nào cũng cần làm vậy, ví dụ tối ưu trả về.
Kéo dài vòng đời tạm bằng rvalue
Về mặt ngữ nghĩa, tạm có thể gây copy/move thêm; dù compiler thường tối ưu bằng copy elision, tham chiếu có thể ép compiler tránh thao tác dư thừa, giảm bất định.
Tài liệu tham khảo
- C++ Reference — reference declaration
- C++ Reference — value categories
- Does const ref lvalue to non-const func return value specifically reduce copies?
Last updated on this page:, Update history
Found an error? Want to help improve? Edit this page on GitHub!
Contributors to this page:OI-wiki
All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply