Почему частные переменные описаны в общедоступном заголовочном файле?

11

Хорошо, так что, надеюсь, это достаточно субъективный вопрос для программистов, но здесь идет. Я постоянно расширяю свои знания языков и практики разработки программного обеспечения ... и столкнулся с чем-то, что просто не имеет для меня никакого смысла.

В C ++ объявления классов включают private:методы и параметры в заголовочный файл, что, теоретически, это то, что вы передаете пользователю, чтобы включить, если вы сделаете их библиотекой lib.

В Objective-C, в @interfaceосновном , делают то же самое, заставляя вас перечислять свои закрытые члены (по крайней мере, есть способ получить закрытые методы в файле реализации).

Из того, что я могу сказать, Java и C # позволяют вам предоставлять интерфейс / протокол, который может объявлять все общедоступные свойства / методы и дает кодировщику возможность скрывать все детали реализации в файле реализации.

Зачем? Инкапсуляция является одним из основных принципов ООП, почему C ++ и Obj-C не имеют этой базовой способности? Есть ли какой-нибудь лучший метод обхода Obj-C или C ++, который скрывает всю реализацию?

Благодаря,

Стивен Фурлани
источник
4
Я чувствую твою боль. Я убежал с криком из C ++ в первый раз, когда добавил частное поле в класс, и мне пришлось перекомпилировать все, что его использовало.
Ларри Коулман
@ Ларри, я не возражаю против этого слишком сильно, но, похоже, это объявляется отличным ОО-языком, но он даже не может инкапсулировать «должным образом».
Стивен Фурлани
2
Не верьте обману. Есть лучшие способы сделать ОО, как в статических, так и в динамических лагерях.
Ларри Коулман
В некотором смысле это отличный язык, но не из-за его ОО-способностей, которые прививают Simula 67 на C.
Дэвид Торнли
Вы должны заняться программированием на Си. Тогда вы лучше поймете, почему эти языки такие, какие есть, включая Java C # и т. Д.
Генри

Ответы:

6

Вопрос в том, должен ли компилятор знать, насколько велик объект. Если это так, то компилятор должен знать о закрытых членах, чтобы подсчитать их.

В Java есть примитивные типы и объекты, и все объекты размещаются отдельно, а переменные, содержащие их, действительно являются указателями. Следовательно, поскольку указатель является объектом фиксированного размера, компилятор знает, насколько велика вещь, которую представляет переменная, не зная фактического размера указанного объекта. Конструктор обрабатывает все это.

В C ++ объекты могут быть представлены локально или в куче. Поэтому компилятору необходимо знать, насколько велик объект, чтобы он мог выделить локальную переменную или массив.

Иногда желательно разделить функциональность класса на общедоступный интерфейс и частное все остальное, и именно здесь появляется метод PIMPL (Pointer to IMPLementation). У класса будет указатель на частный класс реализации, и методы общедоступного класса могут вызывать это.

Дэвид Торнли
источник
не может компилятор обратиться к библиотеке объектов, чтобы получить эту информацию? Почему эта информация не может быть машиночитаемой, а не читаемой человеком?
Стивен Фурлани
@Stephen: во время компиляции не обязательно существует библиотека объектов. Поскольку размер объектов влияет на скомпилированный код, он должен быть известен во время компиляции, а не только во время компоновки. Кроме того, для Ah возможно определить класс A, Bh - для определения класса B, а определения функций в A.cpp - для использования объектов класса B и наоборот. В этом случае необходимо будет скомпилировать каждый из A.cpp и B.cpp раньше другого, если взять размер объекта из кода объекта.
Дэвид Торнли
ссылки не могут быть сделаны во время компиляции? Признаюсь, что не знаю деталей на такой глубине, но если интерфейс объекта раскрывает его содержимое, разве не может быть показано то же содержимое из библиотеки или другого машиночитаемого компонента? они должны быть определены в общедоступном заголовочном файле? Я понимаю , что это часть большинства C языков, но это есть быть так?
Стивен Фурлани
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 / @. В мире C ++ COM вы не раскрываете закрытые переменные каких-либо классов, но можете предоставить файл заголовка - потому что класс является чисто виртуальным. Объект фабрики классов также зарегистрирован для создания реальных экземпляров, и есть несколько метаданных в различных формах.

В C ++ и Objective-C раздавать заголовочный файл меньше, чем писать и поддерживать дополнительный файл interface / @ protocol. Это одна из причин, по которой вы часто видите частную реализацию.

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

JBRWilkinson
источник