Що повинно входити у.


93

При поділі коду на кілька файлів саме те, що саме має перейти у .h-файл, а що - у .cpp-файл?


Пов'язані питання: stackoverflow.com/questions/333889 / ...
Spoike

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

@ThomasMatthews Має сенс. Чи часто використовується така практика?
ty

@lightningleaf: Так, цю практику часто застосовують особливо при змішуванні мов C ++ та C.
Томас Метьюз

Відповіді:


113

Файли заголовків ( .h) призначені для надання інформації, яка буде потрібна у кількох файлах. Такі речі, як декларації класів, прототипи функцій та перерахування, як правило, містяться у заголовкових файлах. Одним словом, "визначення".

Файли коду ( .cpp) призначені для надання інформації про реалізацію, яку потрібно знати лише в одному файлі. Загалом, тіло функцій та внутрішні змінні, до яких інші модулі не повинні / ніколи не матимуть доступу, - це те, що належить до .cppфайлів. Словом, "реалізації".

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


4
За винятком даних приватного класу, вони повинні входити до заголовка. Шаблони повинні бути повністю визначені заголовком (якщо ви не використовуєте один з небагатьох компіляторів, який підтримує export). Єдиний шлях №1 - це PIMPL. # 2 буде можливим, якщо exportвін підтримується, а можливо, можливо, використовуючи c ++ 0x та externшаблони. IMO, файли заголовків у c ++ втрачають значну частину своєї корисності.
KitsuneYMG

23
Все добре, але з неточною термінологією. Словом, "декларації" - термін "визначення" є синонімом "реалізації". У заголовку повинні бути лише декларативний код, вбудований код, визначення макросів та шаблон шаблону; тобто нічого, що створює код або дані.
Кліффорд

8
Я повинен погодитися з Кліффордом. Ви використовуєте декларацію та визначення термінів досить розгублено і дещо взаємозамінне. Але вони мають точне значення в С ++. Приклади: Декларація класу вводить назву класу, але не говорить про те, що в ньому є. Визначення класу перераховує всі члени та функції друзів. І те й інше можна без проблем помістити у заголовки. Те, що ви називаєте "прототип функції", - це оголошення функції . Але визначення функції - це те, що містить код функції і має бути розміщено у файлі cpp - якщо це не вбудований або (частина) шаблон.
селлібіце

5
Вони мають точні значення в C ++, в англійській мові не мають точних значень. Моя відповідь була написана в останньому.
Amber

54

Справа в тому, що в C ++ це дещо складніше, ніж в заголовку / організації джерела.

Що бачить компілятор?

Компілятор бачить один великий джерельний (.cpp) файл із належними заголовками. Вихідний файл - це блок компіляції, який буде зібраний у файл об'єкта.

Отже, навіщо потрібні заголовки?

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

У цьому випадку є дві копії однакової інформації. Що це зло ...

Рішення - поділитися деякими деталями. Хоча реалізація повинна залишатися у Джерелі, декларація спільних символів, як-от функцій, або визначення структур, класів, перерахунків тощо, може бути спільним.

Заголовки використовуються для розміщення цих спільних даних.

Перемістіть до заголовка заяви про те, що потрібно поділити між кількома джерелами

Більше нічого?

У C ++ є деякі інші речі, які можна помістити в заголовок, оскільки їх також потрібно розділити:

  • вбудований код
  • шаблони
  • постійні (зазвичай ті, які ви хочете використовувати всередині комутаторів ...)

Перемістіть до заголовка ВСЕ, що потрібно поділитись, включаючи спільні реалізації

Чи означає це тоді, що всередині заголовків можуть бути джерела?

Так. Насправді, існує багато різних речей, які можуть бути всередині "заголовка" (тобто поділяються між джерелами).

  • Переадресація декларацій
  • декларації / визначення функцій / структур / класів / шаблонів
  • реалізація вбудованого та шаблонного коду

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

Заголовки можна розбити на три частини

Це означає, що в крайньому випадку у вас можуть бути:

  • заголовок прямої декларації
  • заголовок декларації / визначення
  • заголовок реалізації
  • джерело реалізації

Давайте уявимо, що у нас є шаблонний MyObject. Ми могли б мати:

// - - - - MyObject_forward.hpp - - - - 
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;

.

// - - - - MyObject_declaration.hpp - - - - 
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>

template<typename T>
class MyObject
{
   public :
      MyObject() ;
   // Etc.
} ;

void doSomething() ;

.

// - - - - MyObject_implementation.hpp - - - - 
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>

template<typename T>
MyObject<T>::MyObject()
{
   doSomething() ;
}

// etc.

.

// - - - - MyObject_source.cpp - - - - 
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>

void doSomething()
{
   // etc.
} ;

// etc.

Оце Так!

У "реальному житті" це, як правило, менш складно. Більшість кодів матимуть лише просту організацію заголовка / джерела, з деяким вбудованим кодом у джерелі.

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

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

Висновок

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

Але в той день, коли ви матимете кругові залежності між шаблонованими об’єктами, не дивуйтесь, якщо ваша організація коду стане дещо «цікавішою», ніж звичайна організація заголовка / джерела ...

^ _ ^


17

на додаток до всіх інших відповідей, я розповім, що ви не розміщуєте у файлі заголовка:
usingдекларація (найпоширеніша істота using namespace std;) не повинна відображатися у файлі заголовка, оскільки вони забруднюють простір імен вихідного файлу, у який він включений. .


+1 із застереженням, яке ви можете використовувати, якщо воно є у певному просторі імен (або анонімному просторі імен). Але так, ніколи не використовуйте usingдля введення речей у глобальний простір імен у заголовку.
KitsuneYMG

+1 На це набагато легше відповісти. :) Також файли заголовків не повинні містити анонімні простори імен.
sellibitze

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

Стів, те, що ти написав, не переконало мене. Виберіть конкретний приклад, коли ви думаєте, що простір імен anon має загальний сенс у файлі заголовка.
sellibitze

6

Те, що компілюється в ніщо (нульовий двійковий слід), переходить у файл заголовка.

Змінні не складаються в нічого, але декларації типу роблять (тому що вони лише описують, як ведуть себе змінні).

функцій немає, але вбудовані функції роблять (або макроси), оскільки вони виробляють код лише там, де викликаються.

шаблони не є кодом, вони є лише рецептом створення коду. тому вони також ходять у h-файлах.


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

3

Загалом, ви розміщуєте декларації у файлі заголовка та визначення у файлі реалізації (.cpp). Виняток із цього - шаблони, де визначення також має міститись у заголовку.

Це та подібні до нього запитання часто задавались у SO - див. Чому у C ++ файли заголовків та файли .cpp? та файли заголовків C ++, наприклад , розділення коду .


Звичайно, ви також можете помістити визначення класів у файли заголовків. Вони навіть не повинні бути шаблонами.
sellibitze

1

Декларації класу та функції, а також документація та визначення вбудованих функцій / методів (хоча деякі воліють розміщувати їх в окремих файлах .inl).


1

В основному файл заголовка містить скелет класу або декларацію (не змінюється часто)

і cpp-файл містить реалізацію класу (часто змінюється).


5
Будь ласка, утримуйтесь від використання нестандартної термінології. Що таке "скелет класу", що таке "реалізація класу"? Крім того, те, що ви називаєте декларацією в контексті класів, ймовірно, включає визначення класів.
sellibitze

0

файл заголовка (.h) повинен бути для оголошень класів, структур та їх методів, прототипів тощо. Реалізація цих об'єктів здійснюється в cpp.

у .h

    class Foo {
    int j;

    Foo();
    Foo(int)
    void DoSomething();
}

0

Я очікую побачити:

  • декларації
  • коментарі
  • визначення, позначені в рядку
  • шаблони

Дійсно відповідь, хоча це те, що не потрібно вносити:

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

1
Ви також можете помістити визначення класів у файли заголовків. Декларація класу нічого не знаю про своїх членів не говорить.
sellibitze

0

Заголовок Дещо визначає, але нічого не говорить про реалізацію. (Виключаючи шаблони в цьому "метафорі".

З огляду на це, вам потрібно розділити "визначення" на підгрупи, у цьому випадку є два типи визначень.

  • Ви визначаєте "макет" своєї структури, розповідаючи лише стільки, скільки потрібно оточуючим групам використання.
  • Визначення змінної, функції та класу.

Зараз я, звичайно, говорю про першу підгрупу.

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

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


0
  • Заголовкові файли - не повинні змінюватися під час розробки занадто часто -> вам слід подумати і написати їх одразу (в ідеальному випадку)
  • Вихідні файли - зміни під час реалізації

Це одна практика. Для деяких менших проектів це може бути шлях. Але ви можете спробувати припинити функціонування та їх прототипи (у файлах заголовків), а не змінювати їх підпис чи видаляти. Принаймні до зміни основного числа. Як і коли 1.9.2 стикається до 2.0.0 бета-версії.
TamusJRoyce

0

Заголовок (.h)

  • Макроси та включення, необхідні для інтерфейсів (якомога менше)
  • Оголошення функцій та класів
  • Документація інтерфейсу
  • Декларація вбудованих функцій / методів, якщо такі є
  • екстерном до глобальних змінних (якщо такі є)

Тіло (.cpp)

  • Решта макросів та включає
  • Включіть заголовок модуля
  • Визначення функцій та методів
  • Глобальні змінні (якщо такі є)

Як правило, ви розміщуєте "спільну" частину модуля на .h (частину, яку повинні бачити інші модулі) та "не поділену" частину на .cpp

PD: Так, я включив глобальні змінні. Я використовував їх кілька разів, і важливо не визначати їх у заголовках, інакше ви отримаєте багато модулів, кожен із яких визначає свою змінну.

EDIT: Змінено після коментаря Девіда


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