Friday, May 22, 2015

Sử dụng Constant Reference trong hàm

1. Constant(const)
- Dùng cho biến:  Điều kiện ràng buộc báo cho compiler biết giá trị của biến không được thay đổi.
- Dùng cho hàm: Điều kiện ràng buộc báo cho compiler biết giá trị của class không được thay đổi khi gọi hàm này.
Từ khóa const chỉ có tác dụng với compiler trong quá trình compile. Các thao tác thay đổi giá trị của biến trong lúc runtime(thông qua truy xuất vùng nhớ) điều được.

class A  
{  
public:  
 int m_nValue;  
};  
   
//Su dung  
const A a1;  
A a2;  
a1.m_nValue = 10; //Error  
a2.m_nValue = 10; //NOT error  
  
class B  
{  
public:  
    int m_nValue;  
  
    int Function1() const  
    {  
        m_nValue = 10; //Error  
        return m_nValue;  
    }  
  
    int Function2() const  
    {  
        char* p = (char*)this;  
        (int*)p = 10;     //NOT error. Gán giá trị 10 cho biến m_nValue 
                          //thông qua truy xuất vùng nhớ.  
        return m_nValue;  
    }  
}; 
- Ràng buộc của const tác dụng lên thành phần bên trái của nó, nếu không có thành phần bên trái thì nó sẽ tác dụng lên thành phần bên phải.

int const * nVal = 10  //(1)
const int * nVal = 10; //(2)
int * const nVal = 10; //(3)
void function(int const * const nVal) const; //(4)
  • (1): khai báo biến con trỏ(nVal) trỏ tới giá trị constant integer, nghĩa là KHÔNG được thay đổi giá trị 10. Ở đây const tác dụng vào thành phần bên phải là int vì không có thành phần bên trái nó.
  • (2): ý nghĩa tương tự như (1). Ở đây const tác dụng vào thành phần bên trái là int.
  • (3): khai báo biến constant pointer của kiểu int. Nghĩa là có thể thay đổi giá trị 10 nhưng KHÔNG thể thay đổi con trỏ của biến nVal sang ô nhớ khác.
  • (4): khai báo hàm constant -> KHÔNG được thay đổi giá trị trong lớp chứa hàm. Tham số truyền vào là một constant pointer trỏ tới một constant value -> KHÔNG được thay đổi địa chỉ của con trỏ và KHÔNG được thay đổi giá trị của kiểu int được truyền vào.

2. Reference(&)
- Lấy địa chỉ của biến. - Thường được dùng kết hợp với const khi truyền tham số vào hàm.

int 	n 	= 10;
int* 	pVal 	= &n; //Lay dia chi cua bien n gan vao con tro pVal

*pVal = 	5;      //n cung se bang 5
Ví dụ trên, lấy địa chỉ của biến n gán vào con trỏ pVal hay trỏ con trỏ pVal vào vùng nhớ của biến n. Khi đó nếu thay đổi giá trị của n thì giá trị trong vùng nhớ được con trỏ pVal trỏ tới cũng sẽ thay đổi theo, hay ngược lại.

3. Hàm
- Thực hiện một đoạn code và trả về kết quả.
- Cách hoạt động của hàm trong một class:
+ Hàm không tham số, không kết quả trả về: địa chỉ của hàm và con trỏ this của đối tượng(thực thể của class) được đẩy vào vùng nhớ stack rồi sau đó CPU thực thi hàm với đối tượng this. Sau khi thực thi xong, do không có trả kết quả về(kiểu return void) nên kết thúc quá trình thực hiện, các giá trị được đẩy ra khỏi.
+ Hàm có tham số và kết quả trả về: các biến được, địa chỉ hàm, con trỏ this lần lượt được đẩy vào stack. CPU thực hiện hàm, kết quả trả về được đẩy vào một thanh nhớ trên CPU. Kết thúc hàm các giá trị được đẩy ra khỏi stack.

** Hàm có sử dụng const reference và không sử dụng const reference:

struct Data
{
    int id;
    int Value;
};

Data DoSomething1(Data data); //function signature
Data DoSomething2(const Data& data);

//Su dung
Data data = 10;
Data ret1 = DoSomething1(data);
Data ret2 = DoSomething2(data);
Hàm DoSomething1: không sử dụng constant reference
+ B1: giá trị của biến data được copy vào stack.
+ B2.1: gọi instruction call để thực thi hàm.
+ B2.2: ghi kết quả trả về(kiểu Data) vào thanh nhớ.
+ B2.3: pop các giá trị ra khỏi stack.
+ B3: copy giá trị trả về từ thanh nhớ vào biến ret1.

Hàm DoSomething2: sử dụng constant reference
+ B1: địa chỉ của biến data được copy vào stack.
+ B2.1: gọi instruction call để thực thi hàm.
+ B2.2: ghi kết quả trả về(kiểu Data) vào thanh nhớ.
+ B2.3: pop các giá trị ra khỏi stack.
+ B3: copy giá trị trả về từ thanh nhớ vào biến ret2.

Có sự khác nhau ở bước B1 trong quá trình thực hiện 2 hàm. Ở hàm DoSomething1 thì data được copy vào stack và size của data là 8byte, ở hàm DoSomething2 địa chỉ của data được copy vào stack, size của một địa chỉ trên kiến trúc 32bit là 4byte(32bit). Như vậy nếu struct Data có size lớn (giả sử) khoảng 10Kb thì mổi lần thực hiện hàm DoSomething1 sẽ tốn 10Kb memory khi copy vào stack, trong khi với hàm DoSomething2 thì luôn luôn tốn 4byte. Xem thêm cách CPU thực thi một hàm
Tương tự khi xài const reference cho biến trả về của hàm, thì hàm cũng sẽ trả về một địa chỉ thay vì cả data, câu lệnh chạy nhanh hơn.

const Data& DoSomething3();
Hàm DoSomething3 trả về là một const reference. Khi sử dụng kiểu trả về này cần lưu ý, giá trị trong biến trả về KHÔNG được là biến local của hàm. Vì biến local sau khi ra khỏi hàm sẽ bị hủy và kết quả trả về không hợp lệ.

const Data& DoSomething3()
{
    Data data;
    return data; //Error! data là biến local, sẽ bị hủy sau khi ra khỏi hàm
}

Như vậy truyền địa chỉ khi sử dụng hàm sẽ tiết kiệm được memory. Nhưng khi truyền địa chỉ, giá trị của biến truyền vào có thể bị thay đổi trong hàm nên sử dụng kèm const đê không cho thay đổi giá trị lúc thực thi hàm

No comments:

Post a Comment