Hãy kiểm soát “temporary objects” trong C++ | CppDeveloper

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

;

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 →Ta có đoạn code call đến hàm countChar như sau →

Đ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

;

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ụ →Toán tử này sẽ return một object tạm, mỗi khi làm phép toán + như thế này →

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

CHÚC ANH EM CODING VUI VẺ, ÍT BUG, KHÔNG QUẠU NHA :))

— Phạm Minh Tuấn (Shun) —