Với các lập trình viên, chúng ta thường gọi những biến mà chỉ cần thiết trong 1 khoảng thời gian ngắn là “biến tạm” – “temporary variables“, hay “temporary objects“. Hãy xem ví dụ sau →
1 2 3 4 5 6 |
void swap(int& x, int& y) { int temp = x; x = y; y = temp; } |
Chúng ta vẫn thường goị biến temp như ở bên trên là “biến tạm”, ý là chỉ dùng tạm trong 1 vài câu lệnh. Nhưng nếu nhìn rộng ra thì thực ra biến temp bản chất nó cũng giống như các biến local khác, nó sẽ tồn tại cho đến khi function kết thúc, cho nên nếu chỉ gọi nó là biến tạm thì hơi bất công cho nó.
“Biến tạm” thực sự trong C++ sẽ không xuất hiện trong source code của bạn, bạn sẽ không nhìn thấy chúng. Chúng được sinh ra mỗi khi một đối tượng / biến được sinh ra trong stack (non-heap) nhưng không có tên (unnamed). Những biến vô danh này thường được sinh ra ở một trong hai tình huống sau:
- Ép kiểu ngầm định được thực hiện khi call hàm
- Khi một hàm trả về một object
Việc hiểu bản chất các biến tạm này được sinh ra “như thế nào” và “tại sao” chúng cần sinh ra ?, “khi nào” chúng bị hủy ? là rất quan trọng. Bởi vì việc sinh ra và hủy các biến tạm này đi không phải là miễn phí. chương trình của chúng ta sẽ phải trả giá bằng hiệu năng – performance.
Case 1: Ép kiểu ngầm định được thực hiện khi call hàm
Việc này sẽ xảy ra khi kiểu dữ liệu của biến được truyền vào cho function không giống với kiểu dữ liệu của tham số trong khai báo hàm. Ví dụ ta có hàm countChar đếm số lần xuất hiện của một ký tựi trong 1 string được khai báo như sau →
1 |
size_t countChar(const string& str, char ch); |
1 2 3 4 5 6 7 8 9 |
char buffer[MAX_STRING_LEN]; char c; // read in a char and a string; use setw to avoid // overflowing buffer when reading the string cin >> c >> setw(MAX_STRING_LEN) >> buffer; cout << "There are " << countChar(buffer, c) << " occurrences of the character " << c << " in " << buffer << endl; |
Đoạn code này sẽ nhận 1 ký tự và 1 string từ bàn phím do user nhập và in ra kết quả số lần xuất hiện của ký tự trong string. Vấn để trong đoạn code này là chuỗi ký tự được truyền vào cho hàm countChar dưới dạng là char array chứ không phải là đối tượng string như trong khai báo của hàm countChar. Trong trường hợp này compiler sẽ linh hoạt loại bỏ sự sai khác kiểu dữ liệu bằng cách ngấm ngầm tạo ra một “biến tạm” có kiểu là string. Biến tạm này sẽ được khởi tạo bằng việc call đến hàm constructor của string với tham số đầu vào là buffer, sau đó biến tạm này sẽ được truyền vào hàm tham số str của hàm countChar. Sau khi hàm countChar return thì biến tạm sẽ tự động bị hủy.
Việc convert ngầm như thế này đôi lúc khá tiện nhưng đôi lúc cũng gây ra việc tạo/hủy đối tượng không cần thiết, làm giảm performance của chương trình, Như ở ví dụ trên, nếu chúng ta sửa lại, dùng string thay cho buffer để nhận chuỗi ký tự nhập vào ngay từ đầu và dùng nó truyền vào cho hàm countChar thì việc tạo và hủy string khi call hàm countChar sẽ không xảy ra nữa. Kiểu như thế này →
1 2 3 4 5 6 7 8 9 |
string str(MAX_STRING_LEN, '0'); char c; // read in a char and a string; use setw to avoid // overflowing buffer when reading the string cin >> c >> setw(MAX_STRING_LEN) >> str; cout << "There are " << countChar(str, c) << " occurrences of the character " << c << " in " << str << endl; |
Tóm lại là với case này chúng ta có thể hoàn toàn chủ động phòng tránh việc tạo ra “biến tạm” một cách không mong muốn bằng chính code của mình.
Case 2: Function return một object
Trong C++ thì toán tử + thường dùng để tính toán và trả về một object chứa kết quả của phép cộng 2 đối tượng. Ví dụ →
1 |
const Number operator+(const Number& lhs, const Number& rhs); |
1 2 3 |
Number num1, num2; ... Number sum = num1 + num2; |
thì (num1 + num2) sẽ trả về 1 object tạm, sau đó sum sẽ được gán bằng object tạm đó, tiếp theo object tạm bị hủy. Điều này làm giảm performance. Có những trường hợp thay vì dùng toán tử + thì chúng ta có thể chuyển sang toán tử += để tối ưu (vì toán tử += sẽ return reference object chứ không tạo ta object tạm). Ví dụ →
1 2 3 4 5 |
// thay vi code nhu the nay num1 = num1 + num2; // thi code kieu nay num1 += num2; |
Tuy nhiên có thể thấy ngay là cách này không phải lúc nào cũng có thể áp dụng. Và một khi bạn đã call vào cái hàm mà nó trả về object thì object tạm sẽ được tạo ra trong hầu hết các trường hợp.
TÓM LẠI:
- Biến / Đối tượng “tạm” được compiler sinh ra và hủy mà chúng ta không nhìn thấy nó trong source code
- Các Biến / Đối tượng “tạm” được tạo và hủy nhiều khi là không cần thiết và có thể gây ảnh hưởng xấu đến performance của chương trình
- Chúng ta nên hiểu rõ bản chất vấn đề về Biến / Đối tượng “tạm” để có thể chủ động phòng tránh nó gây ảnh hưởng xấu đến performance của chương trình
— Phạm Minh Tuấn (Shun) —