Search…

Forward Declaration trong C/C++

18/09/20205 min read
Lỗi "identifier not found" là một lỗi thường thấy khi build chương trình, đặc biệt là với những chương trình lớn khi mà việc sắp xếp các hàm hoặc trình tự #include các file header trở nên phức tạp. Khi đó việc sử dụng forward declaration là một giải pháp hữu hiệu.

Lỗi "identifier not found"

#include <stdio.h>

int main()
{
	printf("The sum of 1 and 2 is: ", add(1, 2));
	return 0;
}

int add(int a, int b)
{
	return a + b;
}

Khi thực hiện đoạn code trên, kết quả mong đợi hiện lên màn hình là "The sum of 1 and 2 is: 3", nhưng thực tế khi compile chương trình sẽ báo lỗi:

Lỗi "identifier not found"

Lý do xuất hiện lỗi trên là vì compiler compile từng dòng liên tục, nên khi compiler đi đến lời gọi hàm add(1, 2) sẽ không biết add(1, 2) là gì, vì add(int, int) được hiện thực sau đó, điều này gây ra lỗi "identifier not found".

Giải quyết vấn đề

Có hai cách để giải quyết vấn đề này.

Cách 1: Sắp xếp lại, đưa phần định nghĩa hàm add() lên trước hàm main() 

#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int main()
{
	printf("The sum of 1 and 2 is: ", add(1, 2));
	return 0;
}

Bằng cách đó, khi hàm main() gọi hàm add(int, int), trình biên dịch sẽ biết gì add(int, int) ở đâu.

Đây là một chương trình đơn giản nên cách này dễ thực hiện. Nhưng trong một chương trình lớn, cách này có thể rất rối rắm thậm chí không thể sắp xếp được vì có thể hàm trên cần hàm định nghĩa bên dưới và đồng thời hàm dưới cần gọi hàm được định nghĩa bên trên.

Cách 2: Sử dụng forward declaration

Một forward declaration cho phép "hứa hẹn" với compiler về sự tồn tại của một ký hiệu nhận dạng (một type, function hay class) trước khi thực sự định nghĩa nó.

Forward declaration sử dụng prototype cho function

Trong trường hợp của function, khi sử dụng forward declaration nghĩa là báo với compiler có sự tồn tại của hàm đó ngay cả khi chưa định nghĩa nó.

Bằng cách này khi compiler gặp phải một lời gọi của một hàm đã được hẹn trước bằng forward declaration, nó sẽ biết đang thực hiện một lời gọi hàm và kiểm tra cú pháp đã gọi hàm chính xác chưa (các đầu vào đầu ra có hợp lệ không) mặc dù compiler vẫn chưa biết hàm đó thực hiện những công việc gì hay là nó được định nghĩa ở đâu.

Để sử dụng forward declaration cho hàm, sử dụng một câu lệnh declaration được gọi là function prototype (nguyên mẫu hàm). Dưới đây là function prototype cho hàm cộng 2 số:

int add(int, int);

Và đây là chương trình đầy đủ:

#include <stdio.h>

int add(int, int);

int main()
{
	printf("The sum of 1 and 2 is: ", add(1, 2));
	return 0;
}

int add(int a, int b)
{
	return a + b;
}

Forward declaration dành cho class

Đối với những chương trình phức tạp gồm nhiều class, mỗi class được định nghĩa trong file *.h và phần hiện thực trong file *..cpp, khi muốn sử dụng 1 class A được định nghĩa trong file A.h trong class B được định nghĩa trong file B.h, có thể #include "A.h" vào trong file B.h. 

Tuy nhiên, C++ còn cho phép đặt một forward class declaration cho class A trong class B nếu như phần định nghĩa của class B không đề cập đến những nội dụng được định nghĩa trong class A, và nó có dạng như sau:

class A;

Bằng cách này có thể giảm một số lượng các file header cần #include, điều này có thể làm tăng tốc độ compile chương trình.

Bàn luận thêm

Những trường hợp có thể dùng forward class declaration

Với compiler, khi đặt một forward declaration cho class A, compiler biết là class này có tồn tại. Nó không biết gì về kích thước, các thuộc tính hoặc các phương thức của class A. Do đó, không thể sử dụng class A một cách trọn vẹn ở trong class B vì khi đó nó cần biết đầy đủ thiết kế của class A. Điều này dẫn đến chỉ nên dùng forward declaration class A khi phần định nghĩa của class B đạt những yếu tố sau:

  • Chỉ chứa dữ liệu kiểu con trỏ A chứ không chứa dữ liệu kiểu A.
  • Không truy cập bất kỳ phương thức hay thuộc tính nào của A, nếu có nhu cầu muốn sử dụng thì đặt nó trong file B.cpp và #include "A.h" vào file B.cpp.

Ví dụ:

class A;

class B
{
	private:
		A* m_A;
};

Làm thế nào để forward declaration có thể giảm thời gian compile chương trình?

Khi thực hiện compile, đầu tiên compiler sẽ copy toàn bộ nội dung của file.h vào trong những file có #include nó. Nếu như B rất lớn và phức tạp, vậy nên #include "B.h" càng ít càng giúp giảm thời gian compile.

Trường hợp khác, giả sử đã compile thành công chương trình, sau đó muốn thay đổi nội dung trong B.h, và biên dịch lại, khi đó tất cả những file #include "B.h" đều phải recompile, ví dụ như file A.h, khi đó file A.h bị thay đổi, vậy những file #include file A.h cũng phải recompile, ... đối với những chương trình nhỏ, điều này có thể không gây ảnh hưởng nhiều, nhưng với chương trình lớn và phức tạp thì đây lại là một vấn đề lớn.

Do đó nên sử dụng forward declaration và đặt #include file header ở file .cpp để giảm thời gian compile. 

Chuyện gì xảy ra khi dùng forward declaration nhưng quên định nghĩa hàm / class?

Nếu như forward declaration được tạo và không sử dụng tới trong chương trình, thì chương trình vẫn compile thành công và chạy tốt, nhưng nếu như có sử dụng tới nhưng lại chưa định nghĩa thì chương trình vẫn compile tạo được file .obj thành công nhưng linker sẽ báo lỗi vì không giải quyết được lời gọi hàm, class.

Linker error
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