23. Hãy làm cho các File Header tự lập

Estimated reading: 5 minutes 244 views

Tóm tắt

Hãy hành xử có trách nhiệm: Đảm bảo rằng mỗi tập tin header mà bạn viết có thể biên dịch độc lập, bằng cách tự nó #include tất cả các header mà nội dung của nó phụ thuộc vào.

Thảo luận

Nếu một tập tin header không thể hoạt động được trừ khi file sử dụng nó cũng phải  #include một header phụ trợ khác, thì đó là một thiết kế vụng về và gây gánh nặng không cần thiết cho người sử dụng file header đó.

Trước đây, một số chuyên gia từng khuyên không nên #include các header khác trong header của bạn, vì lý do hiệu năng – cụ thể là chi phí khi phải mở và phân tích cú pháp một header đã được bảo vệ (#pragma once hoặc #ifndef/#define) nhiều lần.

Tuy nhiên, lời khuyên này hiện nay đã lỗi thời:

  • Nhiều trình biên dịch C++ hiện đại có thể nhận diện header guard một cách thông minh và không mở cùng một header 2 lần.

  • Một số trình biên dịch còn hỗ trợ precompiled headers (PCH) – giúp đảm bảo các header được dùng thường xuyên và hiếm khi thay đổi sẽ không bị phân tích lại mỗi lần build.

Nhưng hãy lưu ý: Đừng #include những header mà bạn không thực sự cần dùng, vì chúng sẽ tạo ra sự phụ thuộc thừa và làm tăng độ phức tạp.

Một gợi ý về kỹ thuật để đảm bảo header của bạn “tự lập” đó là trong quá trình build, hãy biên dịch từng header một cách riêng biệt (độc lập) để kiểm tra rằng không có lỗi hoặc cảnh báo nào xảy ra nếu thiếu #include file nào đó.

Ví dụ

Một số vấn đề tinh vi phát sinh khi làm việc với template.

Ví dụ 1: Tên phụ thuộc (Dependent names)

Các template được biên dịch tại điểm định nghĩa (point of definition), ngoại trừ các tên hoặc kiểu phụ thuộc (dependent names/types) – những cái này sẽ chỉ được biên dịch tại điểm khởi tạo (point of instantiation). Giả sử bạn có:

				
					template<class T>
class Widget {
    std::deque<T> data_;
};				
			

Nếu trong header của Widget, bạn không #include <deque>, thì sẽ không có lỗi biên dịch ngay lập tức – miễn là chưa có ai khởi tạo Widget với một kiểu cụ thể. Tuy nhiên, vì mục tiêu của Widget là được khởi tạo và sử dụng, nên header của Widget rõ ràng nên #include <deque> từ đầu, để tránh lỗi tiềm ẩn khi sử dụng sau này.

Ví dụ 2: Hàm thành viên template và hàm thành viên của lớp template (member function templates, and member functions of templates)

Các hàm thành viên template, hoặc hàm thành viên của một lớp template, chỉ được khởi tạo nếu thực sự được gọi.

				
					template<class T>
class Widget {
public:
    void Transmogrify() {
        std::deque<T> q;
        // xử lý...
    }
};				
			

Giả sử Transmogrify() sử dụng std::deque<T>, nhưng không ai gọi nó → Widget vẫn có thể được khởi tạo và sử dụng bình thường mà không cần #include <deque>. Nhưng nếu bất kỳ caller nào gọi Transmogrify(), thì khi đó phải có #include <deque>, và nếu header không có dòng này, chương trình sẽ lỗi tại nơi sử dụng. Vì vậy, theo mặc định, bạn nên #include <deque> trong header của Widget, bởi vì có thể có người dùng sẽ cần đến hàm đó.

Trong các trường hợp hiếm gặp mà header tốn kém hiệu suất (như <regex> hay <iostream>) được sử dụng chỉ trong một vài hàm ít dùng, bạn có thể tách riêng các hàm đó thành hàm phi thành viên (nonmember), đặt trong một header khác có #include header tốn kém trên. Cách làm này giúp giảm thời gian build, tăng khả năng tái sử dụng và giảm phụ thuộc không cần thiết (Mục 44).

Tham khảo

[Eakos96] §3.2 • [StroustrupOO] §9.2.3 • [SutterOO] §26-30 • [Vandevoorde03] §9-10

Link

Leave a Comment

Chia sẻ:

23. Hãy làm cho các File Header tự lập

Or copy link

CONTENTS