2009-08-06 8 views
9

Tôi đã đọc mọi thứ tôi có thể tìm thấy về chủ đề này, bao gồm một vài thảo luận rất hữu ích trên trang này, hướng dẫn mã hóa của NASA và hướng dẫn của Google C++. Tôi thậm chí đã mua cuốn sách "thiết kế C++ vật lý" được đề xuất ở đây (xin lỗi, quên tên) và có một số ý tưởng hữu ích từ đó. Hầu hết các nguồn dường như đồng ý - các tệp tiêu đề phải được khép kín, tức là chúng bao gồm những gì chúng cần để tệp cpp có thể bao gồm tiêu đề mà không bao gồm bất kỳ phần tử nào khác và nó sẽ biên dịch. Tôi cũng nhận được điểm về việc tuyên bố trước thay vì bao gồm bất cứ khi nào có thể.Chính sách tiêu đề C++ trên các dự án lớn (redux)

Điều đó nói rằng, làm thế nào về nếu foo.cpp bao gồm bar.hqux.h, nhưng nó chỉ ra rằng bar.h bản thân bao gồm qux.h? Nên foo.cpp sau đó tránh bao gồm qux.h? Pro: dọn dẹp foo.cpp (ít tiếng ồn hơn). Con: nếu ai đó thay đổi thanh.h để không còn bao gồm qux.h, foo.cpp bắt đầu không thành công khi biên dịch. Cũng gây ra sự phụ thuộc giữa foo.cppqux.h không rõ ràng.

Nếu câu trả lời của bạn là "một tệp cpp nên # bao gồm mọi tiêu đề cần", được đưa đến kết luận hợp lý có nghĩa là mỗi tệp cpp phải có #include <string>, <cstddef> v.v ... vì hầu hết mã sẽ kết thúc bằng nếu bạn không phải dựa vào một số tiêu đề khác bao gồm cả chúng, cpp của bạn cần phải bao gồm chúng một cách rõ ràng. Điều đó có vẻ như rất nhiều "tiếng ồn" trong các tập tin cpp.

Suy nghĩ?

thảo luận trước:

What are some techniques for limiting compilation dependencies in C++ projects?

Your preferred C/C++ header policy for big projects?

How do I automate finding unused #include directives?

ETA: Lấy cảm hứng từ các cuộc thảo luận trước đó về đây, tôi đã viết một kịch bản Perl để tiếp nhận xét ra từng 'bao gồm' và 'sử dụng', sau đó cố gắng biên dịch lại tệp nguồn, để tìm ra những gì không cần thiết. Tôi cũng đã tìm ra cách tích hợp nó với VS 2005, vì vậy bạn có thể nhấp đúp để đi đến "không sử dụng" bao gồm. Nếu bất cứ ai muốn nó cho tôi biết ... rất nhiều thử nghiệm ngay bây giờ mặc dù.

+0

Tôi đoán rằng đây nên là cộng đồng wiki vì nó có thể chủ quan. – GManNickG

+0

Nếu bạn nhớ tên thật của cuốn sách "thiết kế C++ vật lý" là gì, hãy chỉnh sửa câu hỏi - Tôi muốn kiểm tra nó (nếu tôi chưa có). –

+0

Cuốn sách có thể là "Thiết kế phần mềm C++ quy mô lớn" của John Lakos: http://www.amazon.com/Large-Scale-Software-Addison-Wesley-Professional-Computing/dp/0201633620 –

Trả lời

8

Nếu câu trả lời của bạn là "một tập tin cpp nên #include mỗi tiêu đề nó cần", đưa đến kết luận hợp lý của mình rằng sẽ có nghĩa là khá nhiều mỗi tập tin cpp phải #include <string>, <cstddef> vv từ hầu hết các mã sẽ kết thúc bằng những, và nếu bạn không phải dựa vào một số tiêu đề khác bao gồm cả chúng, thì cpp của bạn cần bao gồm chúng một cách rõ ràng.

Yup. Đó là cách tôi thích nó.

Nếu 'nhiễu' quá nhiều để chịu, bạn có thể có tệp 'toàn cầu' bao gồm tập hợp thông thường bao gồm (như stdafx.h trong nhiều chương trình Windows) và bao gồm bắt đầu mỗi tệp .cpp (cũng giúp với các tiêu đề được biên dịch trước).

+0

Thực ra, điều stdafx là một mẫu mà tôi đang cố gắng tránh xa. Một vấn đề là mã của chúng tôi phải là nền tảng chéo, và theo như tôi có thể thấy pch của không làm việc theo cùng một cách trong VC + + và gcc. Ngoài ra, chúng tôi đã kết thúc với một "n bình phương" bao gồm phức tạp kể từ khi tất cả mọi thứ đã kết thúc bao gồm tất cả mọi thứ, dẫn đến thời gian biên dịch dài. Tôi đoán chúng tôi có thể đặt STL bao gồm ở một nơi phổ biến, nhưng điều đó có nghĩa là mỗi đơn vị biên dịch được họ cho dù họ có muốn hay không. Tôi cũng nhận thấy rằng các tiêu đề STL bao gồm nhau, vì vậy nếu bạn bao gồm (ví dụ) 'chuỗi', bạn có thể không cần 'bộ nhớ' (chỉ là một ví dụ). –

+2

Tôi có thể hiểu tại sao bạn không muốn sử dụng mẫu stdafx.h, nhưng nó có thể hoạt động và đó là một mẫu mà tôi không xem là 'dạng xấu'. Ngoài ra, tôi sẽ không biện hộ ném tất cả mọi thứ và bồn rửa nhà bếp vào tiêu đề stdafx - chỉ các tiêu đề ** thực sự ** được sử dụng khá nhiều ở khắp mọi nơi. Tệp .cpp vẫn có thể bao gồm các tiêu đề khác ít thường xuyên hơn sau tệp stdaf.h.Và tôi không nghĩ rằng tôi sẽ lo lắng về việc bao gồm nhiều tiêu đề cần thiết khác - chúng không ảnh hưởng đến thời gian xây dựng quá nhiều (tệp tiếp theo sẽ không được xử lý). –

2

Tôi nghĩ rằng bạn vẫn nên bao gồm cả hai tệp. Điều này giúp duy trì mã.

// Foo.cpp 

#include <Library1> 
#include <Library2> 

Tôi có thể đọc và dễ dàng xem thư viện nào sử dụng.Nếu Library2 sử dụng Library1, và nó đã chuyển thành này:

// Foo.cpp 

#include <Library2> 

Nhưng tôi vẫn thấy Library1 mã, tôi có thể là một chút bối rối. Thật khó để đoán rằng một số thư viện khác phải bao gồm nó, nhưng nó vẫn là một quá trình suy nghĩ phải xảy ra.

Rõ ràng có nghĩa là tôi không phải đoán, ngay cả đối với thêm một giây vi biên dịch.

+0

Bạn cũng có thể mở rộng thư viện này vào thư viện STL không? ví dụ. nếu một tập tin cpp sử dụng "chuỗi", bạn muốn xem bao gồm? Trong mỗi tệp có sử dụng "chuỗi"? Tôi chắc chắn có thể thấy lý do của bạn cho tiêu đề người dùng, nhưng nhìn vào mã của tôi, hầu hết các tập tin nguồn sẽ phải có một loạt các STL bao gồm ở đầu trang. –

+2

Chờ đợi, bạn sẽ làm gì khác với họ? Nếu lớp của bạn sử dụng các chuỗi trong tiêu đề, nó sẽ đến đó, nhưng nếu nó chỉ là việc thực hiện đã kiện nó, nó sẽ đi vào cpp. Mỗi cpp * phải * bao gồm tiêu đề mà nó cần. – GManNickG

0

Mỗi tệp tiêu đề cung cấp định nghĩa cần thiết để sử dụng dịch vụ của một số loại (định nghĩa lớp, một số macro, bất kỳ điều gì). Nếu bạn đang trực tiếp sử dụng các dịch vụ được hiển thị thông qua tệp tiêu đề, hãy bao gồm tệp tiêu đề đó ngay cả khi một tệp tiêu đề khác cũng bao gồm tệp đó. Mặt khác, nếu bạn chỉ sử dụng các dịch vụ đó gián tiếp, chỉ trong ngữ cảnh của các dịch vụ của tệp tiêu đề khác, không bao gồm tệp tiêu đề trực tiếp.

Theo tôi, đây không phải là một vấn đề lớn. Sự bao gồm thứ hai của một tệp tiêu đề không được phân tích cú pháp đầy đủ; về cơ bản nó đã nhận xét cho tất cả nhưng lần đầu tiên nó được đưa vào. Khi mọi thứ thay đổi và bạn phát hiện ra bạn đang sử dụng một tệp tiêu đề mà bạn không trực tiếp đưa vào, nó không phải là một việc lớn để thêm nó.

2

Tôi nghĩ bạn nên đưa cả hai cho đến khi nó trở nên khó chịu. Sau đó, refactor các lớp học và các tệp nguồn của bạn để giảm khớp nối, với lý do rằng nếu chỉ liệt kê tất cả các phụ thuộc của bạn là hợp lý, thì bạn có thể có quá nhiều phụ thuộc ...

Một thỏa hiệp có thể nói rằng nếu một cái gì đó trong bar.h chứa một định nghĩa lớp (hoặc khai báo hàm) nhất thiết yêu cầu định nghĩa lớp khác từ qux.h, sau đó bar.h có thể được giả định/bắt buộc để bao gồm qux.h và người dùng API trong bar.h không cần phải bận tâm bao gồm cả hai. Vì vậy, giả sử khách hàng muốn nghĩ về chính nó là "thực sự" chỉ quan tâm đến API thanh, nhưng cũng phải sử dụng qux, vì nó gọi hàm bar lấy hoặc trả về một đối tượng qux theo giá trị. Sau đó, nó chỉ có thể quên rằng qux có tiêu đề riêng của nó và tưởng tượng rằng đó là một API thanh lớn được xác định trong bar.h, với qux chỉ là một phần của API đó.

Điều này có nghĩa là bạn không thể đếm, ví dụ: tìm kiếm tệp cpp để đề cập đến số qux.h để tìm tất cả khách hàng của qux. Nhưng tôi chưa bao giờ dựa vào điều đó, vì nói chung nó quá dễ dàng để vô tình bỏ lỡ một cách rõ ràng bao gồm một phụ thuộc đã được bao gồm gián tiếp, và không bao giờ nhận ra bạn đã không liệt kê tất cả các tiêu đề có liên quan. Vì vậy, bạn có lẽ không nên trong bất kỳ trường hợp nào giả định danh sách tiêu đề được hoàn thành, nhưng thay vì sử dụng doxygen (hoặc gcc-M, hoặc bất cứ điều gì) để có được một danh sách đầy đủ các phụ thuộc, và tìm kiếm đó.

+0

Cảm ơn ... đây là một điểm tốt. Rampant bao gồm * có thể * thực sự là một mùi mã, một dấu hiệu của sự liên kết quá mức. Tôi chắc chắn sẽ xem xét điều đó. –

1

Còn nếu foo.cpp bao gồm bar.h và qux.h, nhưng hóa ra là bar.h chính nó bao gồm qux.h? Nên foo.cpp sau đó tránh bao gồm qux.h?

Nếu foo.cpp sử dụng bất kỳ thứ gì từ qux.h trực tiếp, thì phải bao gồm tiêu đề này. Nếu không, kể từ bar.h cần qux.h, tôi sẽ dựa trên bar.h bao gồm mọi thứ cần thiết.

0

Một cách để suy nghĩ xem liệu foo.cpp có sử dụng trực tiếp qux.h hay không, là suy nghĩ về điều gì sẽ xảy ra nếu foo.cpp không còn cần phải bao gồm bar.h. Nếu foo.cpp vẫn cần phải bao gồm qux.h, thì nó sẽ được liệt kê rõ ràng. Nếu nó không còn cần bất cứ điều gì từ qux.h, có lẽ không cần thiết phải bao gồm nó một cách rõ ràng.

0

Quy tắc "không bao giờ tối ưu hóa mà không cần lược tả" áp dụng ở đây, tôi nghĩ vậy.

(Câu trả lời này là thiên về "hiệu suất" trên "lộn xộn"; sự lộn xộn thường có thể được làm sạch lên theo ý muốn, phải thừa nhận là nó được khó khăn hơn sau đó, nhưng hiệu suất tác động tới bạn một cách thường xuyên.)

Thử theo cả hai cách, hãy xem có cải thiện tốc độ đáng kể cho trình biên dịch của bạn hay không và chỉ xem xét xóa các tiêu đề "trùng lặp" - vì các câu trả lời khác chỉ ra, có một hình phạt bảo trì lâu dài mà bạn thực hiện bằng cách xóa các bản sao.

Cân nhắc việc nhận một số thứ như Sysinternals FileMon để xem liệu bạn có thực sự tạo ra lần truy cập hệ thống tệp cho tất cả các bản sao đó hay không (hầu hết các trình biên dịch sẽ không, với bộ bảo vệ tiêu đề chính xác). Kinh nghiệm của chúng tôi ở đây cho thấy tích cực tìm kiếm các tiêu đề là hoàn toàn không sử dụng (hoặc có thể, với khai báo thích hợp) và loại bỏ chúng còn xứng đáng với thời gian và công sức hơn là tìm ra chuỗi bao gồm cho các bản sao có thể. Và một lint tốt (nẹp, PC-Lint, v.v.) có thể giúp bạn với quyết tâm này.

Thậm chí còn xứng đáng với thời gian và nỗ lực của chúng tôi là tìm cách để trình biên dịch xử lý nhiều đơn vị biên dịch cho mỗi lần thực hiện (gần như là một tốc độ tuyến tính cho chúng tôi, đến một điểm - quá trình biên dịch hoàn toàn bị chi phối bởi khởi động trình biên dịch) .

Sau đó, bạn có thể muốn xem xét sự điên rồ "CPP lớn duy nhất". Nó có thể khá ấn tượng.