Чому приватні змінні описуються у загальнодоступному файлі заголовка?


11

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

В C ++ класові декларації включають private:методи та параметри у файлі заголовка, який, теоретично, це те, що ви передаєте користувачеві для включення, якщо ви зробите його ліб.

У Objective-C @interfaceзробіть майже те саме, що змушує вас перелічити своїх приватних членів (принаймні, у файлі реалізації є спосіб отримати приватні методи).

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

Чому? Інкапсуляція - один з головних принципів ООП, чому C ++ та Obj-C не мають цієї основної здатності? Чи існує якась найкраща практика для Obj-C або C ++, яка приховує всю реалізацію?

Дякую,


4
Я відчуваю твій біль. Я втік, кричачи від C ++, коли я вперше додав приватне поле до класу і довелося перекомпілювати все, що використовувало.
Ларрі Коулман

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

2
Не вірте шуміху. Існують кращі способи проведення ОО як у статичних, так і в динамічних табірних машинах.
Ларрі Коулман

В чомусь це чудова мова, але не завдяки її здібностям ОО, які є прищепленням Simula 67 на C.
Девід Торнлі

Ви повинні виконати деякі програми програмування на С. Тоді ви будете краще розуміти, чому ці мови є такими, якими вони є, включаючи Java C # тощо
Генрі

Відповіді:


6

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

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

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

Іноді бажано розділити функціональність класу на загальнодоступний інтерфейс та приватний на все інше, і саме тут приходить техніка PIMPL (Вказівник на IMPLementation). Клас матиме вказівник на приватний клас реалізації, і методи відкритого класу можуть викликати що.


не може компілятор звернутися до бібліотеки об'єктів, щоб отримати цю інформацію? Чому ця інформація не може бути машиночитаною, а не читаною людиною?
Стівен Фурлані

@Stephen: На момент компіляції не обов'язково існує бібліотека об'єктів. Оскільки розмір об'єктів впливає на компільований код, він повинен бути відомий під час компіляції, а не лише в час посилання. Крім того, для Ah можна визначити клас A, Bh для визначення класу B, а визначення функції в A.cpp використовувати об'єкти класу B і навпаки. У такому випадку потрібно було б скласти кожен A.cpp і B.cpp перед іншим, якщо взяти розмір об'єкта з коду об'єкта.
Девід Торнлі

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

1
@Stephen: Підключення може бути здійснено лише під час компіляції, якщо всі відповідні компоненти є там. Вже тоді це був би складний процес, що передбачав часткову компіляцію (все, крім розмірів об'єкта), часткове посилання (для отримання розмірів об'єкта), потім остаточну компіляцію та зв'язування. Зважаючи на те, що C і C ++ є відносно старими мовами, цього просто не відбулося. Імовірно, хтось може створити нову мову без файлів заголовків, але це не буде C ++.
Девід Торнлі

14

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

Наприклад, якщо ви визначаєте клас

class Foo {
    public int a;
    private int b;
};

то sizeof(Foo)є sizeof(a) + sizeof(b). Якщо був якийсь механізм розділення приватних полів, то заголовок може містити

class Foo {
    public int a;
};

з sizeof(Foo) = sizeof(a) + ???.

Якщо ви хочете дійсно приховати приватні дані, спробуйте ідіому pimpl, с

class FooImpl;
class Foo {
    private FooImpl* impl;
}

у заголовку та визначення FooImplлише у Fooфайлі реалізації.


Ой. Я не мав уявлення, що це так. Ось чому такі мови, як Java, C # і Obj-C, мають "об'єкти класу", щоб вони могли запитати клас, яким має бути великий об'єкт?
Стівен Фурлані

4
Спробуйте використовувати pimpl, вказівник на приватну реалізацію. Оскільки всі вказівники класу однакового розміру, вам не потрібно визначати клас реалізації, а лише оголошуйте його.
Скотт Уельс

Я думаю, що це повинна бути відповідь замість коментаря.
Ларрі Коулман

1

Все це зводиться до вибору дизайну.

Якщо ви справді хочете приховати дані про приватну реалізацію класу C ++ або Objective-C, ви або надасте один або більше інтерфейсів, які підтримує клас (чистий віртуальний клас C ++, Objective-C @protocol) та / або ви створюєте клас вміє конструювати себе, надаючи статичний заводський метод або класний заводський об'єкт.

Причина того, що приватні змінні піддаються впливу файлу заголовка / оголошення класу / @ інтерфейсу, полягає в тому, що споживачеві вашого класу, можливо, знадобиться створити його новий примірник, а new MyClass()або [[MyClass alloc]init]в клієнтському коді потрібен компілятору, щоб зрозуміти, наскільки великий MyClass об'єкт для того, щоб зробити виділення.

У Java та C # також є свої приватні змінні, детально описані у класі - вони не є винятком із цього, але парадигма інтерфейсу IMO набагато частіше зустрічається з цими мовами. У вас може не бути вихідного коду в кожному випадку, але в складеному / байт-коді є достатньо метаданих для виведення цієї інформації. Оскільки у C ++ та Objective-C немає цих метаданих, єдиним варіантом є фактичні деталі інтерфейсу class / @. У світі С ++ COM ви не можете відкривати приватні змінні будь-яких класів, але вони можуть надавати файл заголовка - тому що клас є чистим віртуальним. Фабричний об’єкт класу зареєстрований і для створення власне екземплярів, і є деякі метадані в різних формах.

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

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

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