Forward declaration và Header file inclusion trong C++, khi nào nên dùng cái nào ?

Forward declaration

Là việc khai báo trước ( xét trong phạm vi của bài viết ) tên của một class khác ( class B ) trong ( và trước phần khai báo ) của class hiện tại ( class A ).

class B;

class A
{
public:
    void method();

private:
    int data;
};

Header file inclusion

Include thư viện có lẽ là không ai không biết, trong bài này chúng ta chỉ nói đến trường hợp include một header file của class khác ( chẳng hạn class B ), vào header file của class hiện tại ( chẳng hạn class A ).

#include "B.h"

class A
{
public:
    void method();

private:
    int data;
};

Đặt vấn đề

Có thể thấy, việc include hay khai báo trước class B trong class A đều phục vụ chung một mục đích đó là để có thể sử dụng class B trong class A. Hai cách làm này chắc chắn không tương đương nhau, vậy chúng khác nhau như thế nào và trong trường hợp nào thì nên dùng Header file inclusion/ Forward declaration.

Khi nào dùng Forward declaration ?

Khi sử dụng Forward declaration class B trong class A, chúng ta làm cho class A biết đến sự tồn tại của class B, tức là chỉ quan tâm đến phần khai báo của class B, mà không cần biết định nghĩa của nó. Điều này làm hạn chế những thứ mà chúng ta có thể làm với class B ở trong class A, và dẫn đến những trường hợp chúng ta nên dùng Forward declaration sau đây:

  •  Trong phần định nghĩa của A chỉ có dữ liệu kiểu con trỏ B thay vì một dữ liệu kiểu B ( B * b, thay vì B b ). Bởi vì khi ta khai báo một kiễu dữ liệu của B ( B b ), compiler cần phải biết toàn bộ class B, nghĩa là cả phần khai báo và phần định nghĩa; điều này là không cần thiết khi chỉ khai báo con trỏ đến B.
  •  Không có bất cứ sự sử dụng định nghĩa của B ( như gọi hàm của B, truy cập dữ liệu của B… ) trong phần định nghĩa của A.
  •  Nếu class B rất lớn và phức tạp, bạn có thể tránh include nó càng ít càng tốt, thay bằng Forward declaration, nhằm giảm thời gian compile
  •  Nếu B thay đổi thường xuyên: trong trường hợp này nếu dùng forward declaration, thì B phải được recompiled, A.cpp nếu có sử dụng phần định nghĩa của B ( như gọi hàm của B… ) thì cũng phải được recompiled, tuy nhiên A.h và bất cứ file nào include A.h đều không phải recompiled. Vì như đã đề cập ở trên, A.h chỉ quan tâm đến sự tồn tại của B, nên những thay đổi ở B cũng không làm thay đổi sự tồn tại đó, do đó A.h không cần recompiled, điều này giúp giảm thời gian biên dịch. ( * )

( * ): So sánh với cách Header file inclusion trong trường hợp này thì nếu B thay đổi, thì cả A, và bất cứ file nào include A.h ( hay B.h ) cũng phải được recompiled, thậm chí nếu chúng không hề sử dụng phần định nghĩa của B. Điều này là dễ hiểu khi mà toàn bộ nội dung của file include ( B.h ) được sao chép và thế chỗ dòng include này ( sau quá trình tiền xử lí – preprocessor )

class B;
class A
{
private:
    B * b_1;    // ok, it's a pointer of type B
    B & b_2;    // ok, it's a reference to type B
    B b;        // WRONG !

public:
    void method()
    {
        b_1->data;   // WRONG, can't access B's data
        *b_1.data;   // WRONG, can't de-reference
        b_1->doSomething();    // WRONG, can't use B's function
    }
};
  • Khi hai class cần include lẫn nhau: ta xem xét trường hợp sau đây:
// A.h
#include "B.h"    // WRONG here
class B;          // This will work
class A
{
    B * b;
};

// B.h
#include "A.h"
class B
{
    A a;
};

Khi biên dịch chương trình sẽ báo lỗi, vì sự include qua về lẫn nhau dẫn đến việc class này sẽ không bao giờ biết được sự tồn tại của class kia ( ngay cả khi đã có include guard hay #pragma once ). Ta giải quyết vấn đề này bằng cách sử dụng Forward declaration cho class A vì class A chỉ chứa con trỏ đến B

Khi nào dùng Header file inclusion ?

Từ những trường hợp của Forward declaration, chúng ta có thể dễ dàng suy ra những trường hợp dùng Header file inclusion:

  • Sử dụng định nghĩa của B ( truy cập dữ liệu, sử dụng hàm của B .. )
  • Sử dụng sizeof ( B ) ( nếu chỉ biết sự tồn tại của B thì không thể dùng được size of đối với trường hợp Forward declaration )
  • Sử dụng RTTI
  • Sử dụng new/delete, copy… cho B
  • Nếu class A kế thừa từ B
  • Có dữ liệu kiểu B ( B b )

Happy coding 🙂

Advertisements

One thought on “Forward declaration và Header file inclusion trong C++, khi nào nên dùng cái nào ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s