Чи справді гарні файли заголовків? [зачинено]


20

Я вважаю файли заголовків корисними під час перегляду вихідних файлів C ++, оскільки вони дають "резюме" всіх функцій та членів даних у класі. Чому так багато інших мов (як Ruby, Python, Java тощо) не мають такої функції? Це область, де корисність С ++ є корисною?


Я не знайомий з інструментами в C і C ++, але більшість редакторів IDE / коду, в яких я працював, мають вигляд "структури" або "контуру".
Майкл К

1
Файли заголовків непогані - вони потрібні! - Дивіться також цю відповідь на SO: stackoverflow.com/a/1319570/158483
Pete

Відповіді:


31

Первісна мета файлів заголовків полягала в тому, щоб дозволити компіляцію та модульність у одному проході у C. Декларувавши методи перед їх використанням, це дозволило отримати лише один пропуск компіляції. Цей вік вже давно минув завдяки нашим потужним комп’ютерам, які можуть робити безперебійну компіляцію без проблем, а іноді навіть швидше, ніж компілятори C ++.

Будучи зворотним сумісним з C, C ++ потрібен для збереження файлів заголовків, але додав багато зверху, що призвело до досить проблематичного дизайну. Детальніше у FQA .

Для модульності потрібні були файли заголовків як метадані про код у модулях. Напр. які методи (і в класах C ++) доступні в якій бібліотеці. Було очевидно, що змусити розробника написати це, тому що час компіляції був дорогим. На сьогоднішній день не є проблемою, щоб компілятор генерував ці метадані з самого коду. Мови Java та .NET роблять це нормально.

Так ні. Файли заголовків непогані. Вони були тоді, коли нам ще довелося мати компілятор і лінкер на окремих дискетах, а компіляція зайняла 30 хвилин. Сьогодні вони лише заважають і є ознакою поганого дизайну.


8
Я погоджуюся з більшістю того, що ви пишете, але останній абзац робить речі дещо простими. Файли заголовків як і раніше служать корисною метою для визначення загальнодоступних інтерфейсів.
Бенджамін Баньє

3
@honk Ось до чого я звертався зі своїм запитанням. Але я думаю, що сучасні ІДЕ (як це запропонував ЕльЮсубов) начебто це заперечують.
Метт Фіхман

5
Ви можете додати, що комітет C ++ працює над Модулями, які дозволять C і C ++ працювати без будь-яких заголовків АБО із заголовками, що використовуються більш ефективно. Це зробило б цю відповідь більш справедливою.
Клаїм

2
@MattFichman: Створення деякого заголовкового файлу - це одне (що більшість редакторів / IDE програміста може певним чином зробити автоматично), але суть щодо громадського API полягає в тому, що його потрібно ефективно визначити та розробити реальним людиною.
Бенджамін Баньє

2
@honk Так. Але немає причини, щоб це було визначено в окремих файлах. Це може бути той самий код, що і реалізація. Так само, як це робить C #.
Ейфорія

17

Хоча вони можуть бути корисними для вас як форма документації, системні файли заголовків надзвичайно неефективні.

C був розроблений так, що кожен пропуск компіляції будує один модуль; кожен вихідний файл збирається в окремому виконанні компілятора. З іншого боку, файли заголовків вводяться в цей етап компіляції для кожного з вихідних файлів, на які вони посилаються.

Це означає, що якщо ваш заголовочний файл включений до 300 вихідних файлів, то він буде розбиратися і компілюватися знову і знову, 300 окремих разів під час створення вашої програми. Точно та сама річ з точно таким же результатом, знову і знову. Це величезна витрата часу і є однією з головних причин, чому програми C і C ++ займають стільки часу.

Усі сучасні мови навмисно уникають цієї абсурдної малоефективності. Натомість, як правило, у компільованих мовах необхідні метадані зберігаються у виводі збірки, дозволяючи складеному файлу діяти як своєрідна довідка швидкого пошуку, що описує, що містить компільований файл. Усі переваги файлу заголовка, створеного автоматично, без додаткової роботи з вашого боку.

По черзі в інтерпретованих мовах кожен завантажений модуль залишається в пам'яті. Посилання або включення або вимагає деякої бібліотеки буде читати і компілювати пов'язаний вихідний код, який залишається резидентом до завершення програми. Якщо ви також вимагаєте цього в іншому місці, ніяких додаткових робіт, оскільки вони вже завантажені.

У будь-якому випадку ви можете "переглядати" дані, створені цим кроком, використовуючи мовні інструменти. Зазвичай IDE матиме браузер класів певного роду. І якщо мова має REPL, вона також часто може використовуватися для створення підсумків документації будь-яких завантажених об'єктів.


5
не зовсім вірно - більшість, якщо не всі, компілятори попередньо компілюють заголовки так, як вони їх бачать, так що включення їх у наступний блок компіляції не гірше, ніж пошук блоку попередньо складеного коду. Він не відрізняється від, скажімо, .NET-коду, який неодноразово «будує» system.data.types для кожного файлу C #, який ви компілюєте. Причина програм C / C ++ займає так багато часу в тому, що вони фактично складені (та оптимізовані). Всі програми .NET повинні бути розібрані в IL, час виконання робить фактичну компіляцію через JIT. Однак, 300 файлів у коді C # також потребує певного часу.
gbjbaanb

7

Поки не ясно, що ви маєте на увазі під переглядом файлів для функцій та членів даних. Однак більшість IDE надають Інструменти для перегляду класу та перегляду членів даних класу.

Наприклад: Visual Studio має, Class Viewі Object browserце чудово забезпечує потрібну функціональність. Як і в наступних знімках екрана.

введіть тут опис зображення

введіть тут опис зображення


4

Додатковим недоліком файлів заголовків є те, що програма залежить від порядку їх включення, а програмісту доводиться робити додаткові кроки для забезпечення коректності.

Це в поєднанні з макросистемою C (успадкована C ++) призводить до багатьох підводних каменів і заплутаних ситуацій.

Для ілюстрації, якщо один заголовок визначав якийсь символ, використовуючи макроси для його використання, а інший заголовок використовував символ по-іншому, наприклад, ім'я для функції, то порядок включення сильно вплине на кінцевий результат. Таких прикладів багато.


2

Мені завжди подобалися файли заголовків, оскільки вони надають форму інтерфейсу до реалізації, а також додаткову інформацію, таку як змінні класу, все в одному простому для перегляду файлі.

Я бачу багато C # коду (для якого не потрібно 2 файли на клас), написаного з використанням 2 файлів на клас - один фактичної реалізації класу, а інший з інтерфейсом у ньому. Ця конструкція хороша для глузування (важлива в деяких системах) і допомагає визначити документацію класу без використання IDE для перегляду зібраних метаданих. Я б зайшов так далеко, щоб сказати свою добру практику.

Тож C / C ++, що позначає еквівалент (різновиди) інтерфейсу у файлах заголовків, - це добре.

Я знаю, що є прихильники інших систем, які їм не подобаються з причин, серед яких "складніше просто вийти з коду, якщо вам доведеться помістити речі у два файли", але моє ставлення до того, що просто злом коду не є хорошою практикою , і як тільки ви почнете писати / розробляти код з трохи більше продуманих питань, ви, звичайно, визначатимете заголовки / інтерфейси.


Інтерфейс та реалізація в різних файлах? Якщо вам потрібно це в дизайні, це все ще дуже часто визначати інтерфейс (або абстрактний клас) мовами, такими як Java / C #, в одному файлі та приховувати реалізацію (и) в інших файлах. Для цього не потрібні старі та складні файли заголовків.
Петтер Нордландер

так? це не те, що я сказав - ви визначаєте інтерфейс та реалізацію класу у 2-х файлах.
gbjbaanb

2
добре, що не потрібні файли заголовків, і це не робить файли заголовків кращими. Так, вони потрібні в мовах, які мають спадщину, але як концепція вони не потрібні для розділення інтерфейсу / реалізації на різні файли (насправді вони роблять це гірше).
Петтер Нордландер

@Petter Я думаю, що ви неправильно розумієте, концептуально вони нічим не відрізняються - заголовок існує, щоб забезпечити інтерфейс мовою C, і хоч ви маєте рацію - ви можете комбінувати 2 (як це багато з кодом C лише для заголовка), це не найкраща практика, яку я бачив у багатьох місцях. Наприклад, всі C # розробки, які я бачив, розділили інтерфейс від класу. Це добре, щоб зробити це, оскільки тоді ви можете включати файл інтерфейсу в безліч інших файлів без забруднення. Тепер, якщо ви вважаєте, що цей розкол є найкращою практикою (він є), тоді спосіб досягти цього в C - це за допомогою файлів заголовків.
gbjbaanb

Якщо заголовки насправді були корисними для розділення інтерфейсу та реалізації, такі речі, як PImpl, не знадобляться, щоб приховати приватні компоненти та відключити кінцевих користувачів від необхідності перекомпілювати проти них. Натомість ми отримуємо найгірше з обох світів. Заголовки зображають швидко розпущений фасад поділу, відкриваючи безліч підводних каменів, для яких потрібен багатослівний код котла, якщо вам потрібно обійти їх.
підкреслюй_d

1

Я б фактично сказав, що файли заголовків не дуже великі, оскільки вони замучують інтерфейс та реалізацію. Завданням програмування взагалі, особливо для OOP, є встановлення визначеного інтерфейсу та приховування деталей реалізації, але файл заголовка C ++ показує вам як методи, успадкування, так і публічні члени (інтерфейс), а також приватні методи та приватні члени (деяка частина реалізації). Не кажучи вже про те, що в деяких випадках ви вказуєте вбудований код або конструктори у файлі заголовка, а деякі бібліотеки містять шаблон шаблону в заголовках, що дійсно змішує реалізацію з інтерфейсом.

Первісний намір, я вважаю, полягав у тому, щоб кодом можна було використовувати інші бібліотеки, об'єкти тощо без імпорту всього вмісту сценарію. Все, що вам потрібно, це заголовок для складання та посилання. Це економить час і цикли таким чином. У цьому випадку це гідна ідея, але це лише один із способів вирішення цих питань.

Що стосується перегляду структури програми, то більшість IDE надають таку можливість, і є багато інструментів, які виганяють інтерфейси, роблять аналіз коду, декомпіляцію тощо, щоб ви могли бачити, що відбувається під кришками.

Що стосується того, чому інші мови не реалізують ту саму функцію? Що ж, тому що інші мови походять від інших людей, а ті дизайнери / творці мають інше бачення того, як все має працювати.

Найкраща відповідь - дотримуватися того, що виконує робота, яку вам потрібно зробити, і робить вас щасливими.


0

У багатьох мовах програмування, коли програма підрозділяється на кілька одиниць компіляції, код в одному блоці зможе використовувати речі, визначені в іншому, лише якщо у компілятора є певні засоби знати, що це. Замість того, щоб вимагати, щоб компілятор, який обробляє один компіляційний блок, вивчав увесь текст кожного блоку компіляції, який визначає все, що використовується в цьому блоці, найкраще, щоб компілятор отримував, обробляючи кожну одиницю, повний текст одиниці, який повинен бути складений уздовж з невеликою кількістю інформації від усіх інших.

В C програміст відповідає за створення двох файлів для кожного блоку - один, що містить інформацію, яка буде потрібна лише під час компіляції цього блоку, і один, що містить інформацію, яка також буде потрібна при компілюванні інших підрозділів. Це досить неприємний, хакітний дизайн, але він уникає необхідності мати стандарт мови, який би вказував що-небудь про те, як компілятори повинні генерувати та обробляти будь-які проміжні файли. Багато інших мов використовують інший підхід, при цьому компілятор генерує з кожного вихідного файлу проміжний файл, що описує все у цьому вихідному файлі, який повинен бути доступний зовнішньому коду. Цей підхід дозволяє уникнути необхідності дублювати інформацію у двох вихідних файлах, але він вимагає, щоб платформа компіляції визначала семантику файлів таким чином, що не потрібно для C.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.