Должен ли С++ исключать файлы заголовков?

Многие языки, такие как Java, С#, не отделяют декларацию от реализации. С# имеет понятие частичного класса, но реализация и декларация все еще остаются в одном файле.

Почему у С++ нет такой же модели? Полезнее ли иметь файлы заголовков?

Я имею в виду текущие и будущие версии стандарта С++.

Ответ 1

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

И да, я знаю о частичных классах и #областях, но это не то же самое. Частичные классы на самом деле делают проблему хуже, потому что определение класса распространяется по нескольким файлам. Что касается области #regions, они никогда не расширяются так, как мне хотелось бы, что я делаю в данный момент, поэтому мне приходится тратить время на расширение этих маленьких плюсов, пока я не получу представление правильно.

Возможно, если Visual Studio intellisense лучше работала для С++, у меня не было бы веской причины часто обращаться к файлам .h, но даже в VS2008 С++ intellisense не может касаться

С#

Ответ 2

Обратная совместимость. Заголовочные файлы не устраняются, так как они нарушают обратную совместимость.

Ответ 3

Файлы заголовков допускают независимую компиляцию. Вам не нужно иметь доступ или даже иметь файлы реализации для компиляции файла. Это может упростить распределенные сборки.

Это также позволяет сделать SDK немного проще. Вы можете предоставить только заголовки и некоторые библиотеки. Конечно, есть способы, которыми пользуются другие языки.

Ответ 4

Даже Bjarne Stroustrup назвал файлы заголовков kludge.

Но без стандартного двоичного формата, который включает в себя необходимые метаданные (например, файлы классов Java или файлы .Net PE), я не вижу возможности реализовать эту функцию. Разделенный ELF или бинарный файл a.out не имеют большой информации, которую вам нужно будет извлечь. И я не думаю, что информация всегда хранится в файлах Windows XCOFF.

Ответ 5

В Дизайн и эволюция С++, Stroustrup дает еще одну причину...

Один и тот же заголовочный файл может иметь два или более файлов реализации, которые могут одновременно обрабатываться более чем одним программистом без использования системы контроля источника.

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

Ответ 6

C было сделано для того, чтобы писать компилятор легко. Он делает много вещей, основанных на этом принципе. Указатели существуют только для упрощения написания компилятора, как и файлы заголовков. Многие из вещей, перенесенных на С++, основаны на совместимости с этими функциями, реализованными для упрощения написания компилятора.

Это хорошая идея. Когда C был создан, C и Unix представляли собой пару. C портировал Unix, Unix работал C. Таким образом, C и Unix могли быстро распространяться с платформы на платформу, тогда как ОС на основе сборки пришлось полностью переписать для переноса.

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

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

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


Кажется, что некоторые люди действительно не верят, что C был записан в порт Unix, поэтому здесь: (from)

Была написана первая версия UNIX на языке ассемблера, но Томпсон что было бы написано на языке высокого уровня.

Томпсон впервые попытался в 1971 году использовать Fortran на PDP-7, но сдался после первого дня. Затем он написал очень простой язык, который он назвал B, который он получил на PDP-7. Это работали, но были проблемы. Во-первых, поскольку реализация интерпретируется, это всегда будет медленный. Во-вторых, основные понятия B, которая была основана на словосочетании BCPL, просто не были подходящими для байт-ориентированная машина, как новая PDP-11.

Ритчи использовал PDP-11 для добавления типов к B, который некоторое время назывался NB для "New B", а затем он начал напишите компилятор для него. "Таким образом первая фаза C была действительно этими двумя фазы в короткой последовательности, во-первых, некоторые изменения языка от B, действительно, добавление структуры типа без значительное изменение синтаксиса; и делает компилятор, - сказал Ричи.

" Вторая фаза была медленной ", - сказал он. переписывания UNIX в C. Thompson началось летом 1972 года, но две проблемы: выяснить, как работать основные совлокальные процедуры, то есть, как переключить управление с одного процесса на другой; и трудности с получением надлежащей структуры данных, поскольку оригинальной версии C не было структуры.

" Сочетание вещей, вызванных Кен, чтобы отказаться от лета, "- сказал Ричи." В течение года я добавил структур и, возможно, код компилятора несколько лучше - лучший код - и так далее летом, когда мы сделали согласованные усилия и на самом деле сделали повтор всей операционной системы в C. "


Вот прекрасный пример того, что я имею в виду. Из комментариев:

Указатели существуют только для упрощения написания компилятора? Нет. Указатели существуют, потому что они являются простейшей возможной абстракцией над идеей косвенности. - Адам Розенфилд (час назад)

Вы правы. Для реализации косвенности указатели являются простейшей возможной абстракцией для реализации. Ни в коем случае они не являются самыми простыми в постижении или использовании. Массивы намного проще.

Проблема? Чтобы реализовать массивы так же эффективно, как указатели, вы должны в значительной степени добавить HUGE кучу кода в свой компилятор.

Нет причин, по которым они не могли бы спроектировать C без указателей, но с таким кодом:

int i=0;
while(src[++i])
    dest[i]=src[i];

вам потребуется много усилий (в части компиляторов), чтобы разделить явные дополнения я + src и я + dest и создать тот же код, что и это:

while(*(dest++) = *(src++))
    ;

Факторизация этой переменной "i" после того, как факт является HARD. Новые компиляторы могут это сделать, но тогда это было просто невозможно, а ОС, работающая на этом дрянной аппаратуре, нуждалась в небольших оптимизациях.

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

Ответ 7

Если вы хотите С++ без файлов заголовков, то у меня есть хорошие новости для вас.

Он уже существует и называется D (http://www.digitalmars.com/d/index.html)

Технически D кажется намного более приятным, чем С++, но это просто недостаточно для использования во многих приложениях на данный момент.

Ответ 8

Один из целей С++ - это надмножество C, и для него это сложно сделать, если он не может поддерживать файлы заголовков. И, в добавлении, если вы хотите вырезать заголовочные файлы, вы можете также рассмотреть возможность исключения CPP (предварительный процессор, а не плюс плюс); как С#, так и Java не указывают макропроцессоры с их стандартами (но следует отметить, что в некоторых случаях они могут быть и даже использоваться даже с этими языками).

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

Ответ 9

Многие люди знают о недостатках файлов заголовков, и есть идеи по внедрению более сильной модульной системы на С++. Вы можете взглянуть на Модули в С++ (версия 5) от Daveed Vandevoorde.

Ответ 10

Одним из преимуществ этого разделения является то, что легко видеть только интерфейс без, требующий расширенного редактора .

Ответ 11

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

Ответ 12

Существует значение при определении интерфейса класса в отдельном компоненте в файле реализации.

Это можно сделать с помощью интерфейсов, но если вы спуститесь по этой дороге, то вы неявно говорите, что классы недостаточны с точки зрения разделения реализации из контракта.

Модула 2 имела правильную идею, модули определения и модули реализации. http://www.modula2.org/reference/modules.php

Ответ Java/С# является неявной реализацией того же (хотя и объектно-ориентированного.)

Заголовочные файлы - это kludge, потому что файлы заголовка выражают детали реализации (например, частные переменные).

При переходе на Java и С# я нахожу, что если язык требует поддержки IDE для разработки (так что интерфейсы открытого класса являются судоходными в браузерах классов), то это, возможно, утверждение, что код не стоит на его что заслуживает особого внимания.

Я считаю, что сочетание интерфейса с деталями реализации довольно ужасает.

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

Ответ 13

Если вам нужна причина, по которой это никогда не произойдет: это сломает практически все существующее программное обеспечение на С++. Если вы посмотрите на некоторые проектные документы комитета С++, они рассмотрели различные варианты, чтобы увидеть, сколько кода он сломал.

Было бы гораздо легче изменить оператор switch на что-то наполовину интеллектуальное. Это сломало бы лишь небольшой код. Это все еще не произойдет.

ДЛЯ НОВОЙ ИДЕИ:

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

Ответ 14

Язык без заголовков не существует. Это миф.

Посмотрите на какой-либо запатентованный дистрибутив библиотеки для Java (у меня нет опыта работы с С#, но я бы ожидал, что он будет таким же). Они не дают вам полный исходный файл; они просто дают вам файл с каждой загнутой реализацией метода ({} или {return null;} или тому подобное) и все, что они могут избежать, скрывая скрытые. Вы не можете назвать это чем угодно, кроме заголовка.

Однако нет никакой технической причины, почему компилятор C или С++ мог считать все в соответствующем файле как extern, если этот файл не скомпилирован напрямую. Однако затраты на компиляцию были бы огромными, потому что ни C, ни С++ не быстро разбираются, и это очень важно. Любой более сложный метод объединения заголовков и источника быстро столкнется с техническими проблемами, такими как необходимость того, чтобы компилятор знал макет объекта.

Ответ 15

Заголовочные файлы являются неотъемлемой частью языка. Без файлов заголовков все статические библиотеки, динамические библиотеки, почти любая предварительно скомпилированная библиотека становятся бесполезными. Файлы заголовков также облегчают документирование всего и позволяют просматривать API-интерфейс библиотеки/файла без перебора каждого отдельного кода.

Они также упрощают организацию вашей программы. Да, вы должны постоянно переключаться с источника на заголовок, но они также позволяют вам определять внутренние и частные API-интерфейсы внутри реализаций. Например:

MySource.h:

extern int my_library_entry_point(int api_to_use, ...);

MySource.c:

int private_function_that_CANNOT_be_public();

int my_library_entry_point(int api_to_use, ...){
  // [...] Do stuff
}

int private_function_that_CANNOT_be_public() {

}

Если вы #include <MySource.h>, то получите my_library_entry_point.

Если вы #include <MySource.c>, то вы также получите private_function_that_CANNOT_be_public.

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

Ответ 16

О да!

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

На самом деле это очень легко. Просто поставьте определение (реализацию) внутри раздела #ifdef и добавьте определение в командной строке компилятора для компиляции этого файла. Что это.

Вот пример:

/* File ClassA.cpp */

#ifndef _ClassA_
#define _ClassA_

#include "ClassB.cpp"
#include "InterfaceC.cpp"

class ClassA : public InterfaceC
{
public:
    ClassA(void);
    virtual ~ClassA(void);

    virtual void methodC();

private:
    ClassB b;
};

#endif

#ifdef compiling_ClassA

ClassA::ClassA(void)
{
}

ClassA::~ClassA(void)
{
}

void ClassA::methodC()
{
}

#endif

В командной строке скомпилируйте этот файл с помощью

-D compiling_ClassA

Другие файлы, которые должны включать ClassA, могут просто делать

#include "ClassA.cpp"

Конечно, добавление определения в командной строке может быть легко добавлено с расширением макроса (компилятор Visual Studio) или с автоматическими переменными (gnu make) и с использованием той же номенклатуры для имени определения.

Ответ 17

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