Урок №130. Класи і заголовкові файли

  Юрій  | 

  Оновл. 10 Лют 2021  | 

 54

На цьому уроці ми розглянемо роботу класів з заголовковими файлами в мові С++.

Відокремлення оголошення від реалізації

Всі класи, які ми використовували до цього моменту, були досить простими, тому ми записували методи безпосередньо всередині тіла класів, наприклад:

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

На щастя, мова C++ надає спосіб відокремити «оголошення» від «реалізації». Це робиться шляхом визначення методів поза тілом самого класу. Для цього просто визначте методи класу, так наче це звичайні функції, але в якості префіксу додайте до імені функції ім’я класу з оператором дозволу області видимості (::).

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

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

Ось ще один приклад класу з конструктором, визначеним ззовні, зі списком ініціалізації членів:

Конвертуємо в:

Класи і заголовкові файли

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

Ось наш клас Date, але вже розбитий на файли .cpp і .h:

Date.h:

Date.cpp:

Тепер будь-який інший файл .h або .cpp, який захоче використати клас Date, зможе просто підключити заголовок: #include "Date.h". Зверніть увагу, Date.cpp також необхідно додати до компіляції в проект, який використовує Date.h, щоб лінкер зміг розібратися з реалізацією класу Date.

Питання №1: “Хіба визначення класу в заголовку не порушує правило одного визначення?”.

Ні. Класи — це користувацькі типи даних, які звільняються від визначення тільки в одному місці. Тому клас, визначений у заголовку, можна вільно підключати в інші файли.

Питання №2: “Хіба визначення методів класу в заголовку не порушує правило одного визначення?”.

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

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

Параметри за замовчуванням

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

Бібліотеки

Відокремлення оголошення класу від його реалізації дуже поширене в бібліотеках, які використовуються для розширення можливостей вашої програми. Ви також підключали такі заголовки зі Стандартної бібліотеки С++, як: iostream, string, vector, array і інші. Зверніть увагу, ви не додавали iostream.cpp, string.cpp, vector.cpp або array.cpp в ваші проекти. Ваша програма потребує тільки оголошення з заголовкових файлів, щоб компілятор зміг перевірити коректність вашого коду відповідно до правил синтаксису мови C++. Однак реалізації класів, які перебувають в Стандартній бібліотеці С++, містяться в попередньо скомпільованому файлі, який додається на етапі лінкінгу. Ви ніде не зустрічаєте цей код.

Поза програм з відкритим вихідним кодом (де надаються обидва файли: .h і .cpp), більшість сторонніх бібліотек надають тільки заголовки разом з попередньо скомпільованим файлом бібліотеки. На це є декілька причин:

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

   Захист інтелектуальної власності (творці не хочуть, щоб інші просто “крали” їх код).

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

Висновки

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

   По-перше, як згадувалося вище, це призведе до захаращення визначення вашого класу.

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

   По-третє, якщо ви зміните що-небудь в заголовковому файлі, то вам потрібно буде перекомпілювати кожен файл, який містить цей заголовок. Це може мати “ефект метелика”, коли одна незначна зміна коду змусить перекомпілювати всю програму (що може бути досить повільно і довго). Якщо ж ви змінили код у файлі .cpp, то вам необхідно перекомпілювати тільки цей файл .cpp!

Тому рекомендується наступне:

   Класи, які використовуються тільки в одному файлі, і які повторно не використовуються, визначайте безпосередньо в файлі .cpp, де вони використовуються.

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

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

   Нетривіальні методи визначайте в файлі .cpp з тим же ім’ям, що має клас.

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

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (1 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *