Чому ми ставимо функції приватних членів у заголовки?


16

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

Чому нам потрібно ставити приватних членів у заголовки?

Але чи є підстави оголошувати приватні функції у визначенні класу?

Альтернативою була б по суті ідіома сутенера, але без зайвої непрямості.

Чи є ця мовна особливість більш ніж історичною помилкою?

Відповіді:


11

Функції приватних членів можуть бути virtual, а в загальних реалізаціях C ++ (для яких використовується vtable) необхідний певний порядок та кількість віртуальних функцій, щоб їх знали всі клієнти класу. Це застосовується, навіть якщо є одна або кілька функцій віртуального члена private.

Можливо, здається, що це як "поставлення кошика перед конем", тому що вибір реалізації компілятора не повинен впливати на специфікацію мови. Однак насправді мова C ++ була розроблена одночасно з робочою реалізацією ( Cfront ), яка використовувала vtables.


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

Це звучить дуже правдоподібно. Чи є джерело про це?
Праксеоліт

@Praxeolitic: Ви можете прочитати про таблицю методів Virtual .
Грег Х'югілл

1
@Praxeolitic - знайдіть стару копію "Посібника з анотацією C ++" ( stroustrup.com/arm.html ) - автори розповідають про різні деталі реалізації та про те, як вона формувала мову.
Майкл Коне

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

13

Якщо ви дозволили додавати методи до класу поза його визначенням, вони можуть бути додані будь-де , у будь-якому файлі, будь-ким.

Це негайно дасть усім клієнтському коду тривіальний доступ до приватних та захищених членів даних.

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


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

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


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

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

Це хороший момент, але хіба це неправда для визначень усіх функцій-членів, що не є в заголовку?
Праксеоліт

1
Усі функції учасника декларуються всередині класу. Справа в тому, що ви не можете додавати нові функції-учасники, які не були принаймні задекларовані у визначенні класу (а одне правило визначення перешкоджає численним визначенням).
Марно

Тоді як щодо правила одного контейнера приватних функцій?
Праксеоліт

8

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

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

Отже, я хотів надати відповідь, яка замість того, щоб включати приватні методи за замовчуванням, дає конкретні моменти на користь того, щоб вони були видимі для користувачів. Механістична причина невіртуальних приватних функцій, що вимагають публічного декларування, наведена у статті Herb Sutter's GotW № 100 про ідіому Pimpl як частину її обґрунтування. Я не буду продовжувати про Pimpl тут, оскільки я впевнений, що ми всі про це знаємо. Але ось відповідний біт:

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

  • Розмір і макет : [про членів і віртуальних функцій - зрозуміло і чудово для роботи, але не для того, щоб ми тут]
  • Функції : Код, що викликає, повинен мати можливість вирішувати виклики до функцій учасників класу, включаючи недоступні приватні функції, які перевантажують неприватні функції - якщо приватна функція краще відповідає, викликовий код не вдасться скомпілювати. (C ++ прийняв цілеспрямоване дизайнерське рішення провести вирішення перевантаження перед перевіркою доступності з міркувань безпеки. Наприклад, вважалося, що зміна доступності функції з приватної на публічну не повинна змінювати значення юридичного коду виклику.)

Звісно, ​​Саттер є надзвичайно надійним джерелом як члена Комітету, тому він знає "обдумане дизайнерське рішення", коли його бачить. І думка вимагати публічного декларування приватних методів як способу уникнути зміненої семантики або випадково порушеної доступності пізніше - це, мабуть, найбільш переконливе обґрунтування. Вдячно, тому що вся справа здавалася досить безглуздою раніше!


" Але тоді ви запитуєте чому, і ця відповідь здається тавтологічною. Знову, чому користувачі класу повинні знати про них? " Ні, це не тавтологічно. Користувачам не обов’язково "потрібно знати про них". Що має відбутися, це те, що ви повинні мати можливість не давати людям продовжувати заняття без згоди класного керівника. Тому всі такі члени повинні бути оголошені наперед. Те, що це змушує користувачів знати про приватних членів, є лише необхідним наслідком.
Нікол Болас

1
@NicolBolas точно. Це, мабуть, не дуже вдале формулювання з мого боку. Я мав на увазі, що відповідь пояснює лише видимість приватних методів як наслідок (дуже дійсного) правила, яке охоплює багато речей, а не надає обґрунтування про видимість приватних методів. Звичайно, справжня відповідь - це поєднання непотрібних, гнашерських та моїх. Я просто хочу надати ще одну перспективу, якої ще тут не було.
підкреслюй_d

4

Для цього є дві причини.

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

Лаконічність

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

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

Є ще одна основна перевага, яка ...

Вбудовані функції

Приватна функція може бути впорядкованою, і це обов'язково вимагає, щоб вона була в заголовку. Врахуйте це:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

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


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

@BenVoigt так і є. Я цього не усвідомлював, і досить довго використовую неповний робочий день на C ++. Ця мова ніколи не перестає дивувати мене такими маленькими самородками.

Я не думаю, що метод повинен бути в заголовку, щоб бути накресленим. Просто потрібно знаходитись в одному компілювальному блоці. Чи є випадок, коли є сенс мати окремі одиниці компіляції для класу?
Самуель Даніельсон

@SamuelDanielson правильний, але приватна функція повинна бути у визначенні класу. Саме поняття "приватне" передбачає, що це частина класу. У .cppфайлі може бути функція нечлену, позначена функціями-членами, визначеними поза визначенням класу, але така функція не була б приватною.

Основним питанням було "чи є причина декларувати приватні функції у визначенні класу [sic, за допомогою якого контекст показує, що ОП дійсно означало декларування класу]". Ви говорите про визначення приватних функцій. Це не відповідає на запитання. @SamuelDanielson На сьогоднішній день LTO означає, що функції можуть бути в будь-якому місці проекту та мати однакові шанси бути накресленими. Що стосується розділення класу на кілька одиниць перекладу, найпростіший випадок - клас просто великий і ви хочете семантично розділити його на кілька вихідних файлів. Я впевнений, що такі великі класи відволікають, але все одно
підкреслюй

2

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

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


Відмінний пункт про вбудову! Я думаю, що це особливо актуально для stdlib та інших бібліотек із методами, визначеними вбудованими. (якщо не стільки для внутрішнього коду, де LTO може робити майже все, що завгодно через межі ТУ)
підкреслюйте

0

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

Якщо будь-яка функція могла отримати доступ до приватних членів класу, то приватне було б марним.


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