Search…

Type-casting Trong C++

06/09/20205 min read
Tìm hiểu về chuyển đổi kiểu dữ liệu (type-casting) trong C++.

Bản chất của type-casting không phải là biến đổi kiểu dữ liệu này thành kiểu dữ liệu khác. Thực chất type-casting chỉ thay đổi cách đọc các byte dữ liệu của một biến, hoặc thay đổi độ dài sẽ được đọc của vùng nhớ mà biến đó trỏ tới. Do đó khi casting, dữ liệu sẽ không bị thay đổi, nhưng dữ liệu có thể sẽ được hiển thị khác với lúc ban đầu, tuỳ theo tính tương thích giữa kiểu dữ liệu trước và sau khi casting.

Bản thân toán tử = (operator =) cũng đã được overload lại để phục vụ cho việc casting các kiểu dữ liệu thông thường như int, float, char, … Đối với các kiểu dữ liệu do người dùng định nghĩa, ta cũng có thể overload lại operator = nhưng tồn tại một số nhược điểm như sau:

  • Khai báo phức tạp, tốn công sức và thời gian khi cần casting nhiều kiểu dữ liệu khác nhau.
  • Khi nâng cấp và mở rộng dự án phải bổ sung thêm, dễ sai sót dẫn đến lỗi.
  • Ép kiểu không tường minh đôi lúc gây khó hiểu cho lập trình viên.

C++ đã cung cấp cho chúng ta một số công cụ hỗ trợ việc casting một cách tổng quát, có thể sử dụng với bất kỳ kiểu dữ liệu nào. Sử dụng chúng, ta hạn chế được các nhược điểm kể trên. Đổi lại ta cần cung cấp chi phí bộ nhớ cho chúng để sử dụng.

Cú pháp chung của các kiểu casting:

type_cast <new_type> (expression);

Trong đó, new_type là kiểu dữ liệu cần chuyển đổi; expression là đối tượng cần được chuyển sang kiểu dữ liệu mới. Để lưu trữ lại, ta cần khai báo một biến có kiểu dữ liệu new_type và gán kết quả sau khi casting vào biến đó.

dynamic_cast

dynamic_cast được sử dụng với con trỏ (pointer) hoặc tham chiếu đến các lớp. dynamic_cast sẽ thực hiện quá trình kiểm tra tính hợp lệ của đối tượng trong quá trình thực thi. Nếu đối tượng không tương thích với kiểu dữ liệu mới, chương trình sẽ trả về NULL (đối với con trỏ) hoặc throw exception std::bad_cast (đối với tham chiếu đến các lớp).

dynamic_cast được sử dụng khi ta không biết kiểu dữ liệu thực sự của đối tượng trước khi casting. Do đó nếu đã biết chính xác kiểu dữ liệu của đối tượng thì không nên sử dụng nó vì dynamic_cast đòi hỏi chi phí cao cho việc sử dụng.

Ví dụ sau minh hoạ trường hợp không thể sử dụng kiểu casting nào khác ngoài dynamic_cast:

#include <stdio.h>

class Animal
{
public:
	Animal(){}
	virtual ~Animal(){}
	//...
};

class Cat : public Animal
{
public:
	Cat(){}
	~Cat(){}
	//...
};

class Bird : public Animal
{
public:
	Bird(){}
	~Bird(){}
	//...
};

#define ANIMAL_COUNT 5
int main()
{
	Animal** animal_list = new Animal*[ANIMAL_COUNT];

	for(int i = 0; i < ANIMAL_COUNT; i++)
	{
		if(Some_logical_expressions)
			animal_list[i] = new Cat();
		else
			animal_list[i] = new Bird();
	}

	for(int i = 0; i < ANIMAL_COUNT; i++)
	{
		if(dynamic_cast<Cat*>(animal_list[i]))
		{	
			//Do something
		}
		else if(dynamic_cast<Bird*>(animal_list[i]))
		{
			//Do something else
		}
	}

	for(int i = 0; i < ANIMAL_COUNT; i++)
		delete animal_list[i];
	delete [] animal_list;

	return 0;
}

static_cast

static_cast có nhiều điểm tương đồng với dynamic_cast, ngoại trừ không thực hiện kiểm tra tính tương thích giữa đối tượng và kiểu dữ liệu mới. Do đó, việc kiểm tra tính tương thích và đảm bảo của quá trình casting được trao cho lập trình viên.

Do không kiểm tra tính tương thích giữa đối tượng và kiểu dữ liệu nên static_cast tốn ít chi phí bộ nhớ hơn so với dynamic_cast.

reinterpret_cast

reinterpret_cast thao tác với các con trỏ tương tự như các toán tử chuyển đổi thông thường. Kết quả của việc casting đơn giản là việc copy dữ liệu quản lý bởi một con trỏ sang con trỏ khác. Do đó nó có thể casting giữa tất cả các kiểu dữ liệu, kể cả các lớp không có quan hệ với nhau.

Khi sử dụng reinterpret_cast, lập trình viên cần hiểu rõ mình đang làm gì, vì hầu hết quá trình casting với reinterpret_cast đều hợp lệ và không thông báo lỗi trong quá trình build.

const_cast

const_cast được sử dụng để thêm hoặc loại bỏ const khỏi một biến. const_cast là cách duy nhất để thay đổi tính hằng của biến.

#include <stdio.h>

void echo(char* str)
{
	printf("%s", str);
}

int main()
{
	const char *str = "STDIO Tutorial";

	//echo(str);				//Error incompatible type
	echo(const_cast<char*>(str));	//OK

	return 0;
}

typeid

Chuẩn ANSI-C++ định nghĩa một toán tử mới là typeid, cho phép kiểm tra kiểu dữ liệu của một biến.

typeid được định nghĩa trong header <typeinfo>, do đó các lập trình viên cần khai báo thư viện này trước khi sử dụng.

Cú pháp sử dụng:

typeid (expression)

Kết quả trả về một tham chiếu tới const object của kiểu dữ liệu. Có thể so sánh hai typeid thông qua toán tử ==!= đã được overload. Ngoài ra có thể lấy được một chuỗi kí tự mô phỏng kiểu dữ liệu hoặc tên lớp thông qua phương thức name() của typeid.

Sử dụng lại cấu trúc kế thừa ở ví dụ trên, xét ví dụ sau:

int main()
{
	Animal* animal = new Animal();
	Animal* cat = new Cat();

	//Check to know if 2 variable’s type are the same or not
	if(typeid(animal) == typeid(cat))
		printf("%s\n", typeid(cat).name());
	else
		printf(“type are different\n”);

	printf(typeid(static_cast<Cat*>(cat)).name());


	return 0;
}

Kết quả được in ra trên màn hình:

class Animal *
class Cat *

Dù đã được khởi tạo qua toán tử new, bản chất của lớp Cat vẫn là Animal. Do đó typeid vẫn trả về một const object có kiểu Animal. Qua đó thấy được tầm quan trọng của việc casting trong OOP.

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024