Для чего предназначен файл реализации
В объектно-ориентированном программировании , А файл реализация класса часто используются , чтобы содержать код реализации методы (ов) из в классе . Этот файл также называется исходным файлом . [ необходима цитата ] Языки программирования, такие как C и C ++, используют эти файлы реализации, чтобы разделить интерфейс и реализацию этих методов. [1]
3 ответа 3
Уж сколько раз твердили миру. (с)
Не помещайте определения шаблонных функций/классов в отдельные .cpp-файлы. Размещайте все только в заголовочных файлах - иначе инстанцирования не происходит, вот и имеем, что имеем.
Совет от Страуструпа (A Tour of C++, adv. 7-6):
Вот подумайте сами - откуда при компиляции файла Test.cpp компилятору знать, что (с каким параметром шаблона) будет инстанцироваться в другом файле?
А в этом другом файле (main.cpp) - откуда знать, как инстанцировать шаблон, если нет доступа к коду реализации?
Обходной путь - указать в файле реализации шаблона явное инстанцирование необходимых типов
возможен, но очень уж он неудобный - в следующий раз в main.cpp понадобится класс для нового типа, и надо будет тут же вносить его в test.cpp . Еще хуже, если какой-то тип станет ненужен, но будет продолжать инстанцироваться и линковаться.
@Kaznachei Спасет. Шаблонные функции - всегда автоматически inline . С inline ведь не возникает такой проблемы.
@HolyBlackCat: Формально шаблонные функции не являются "автоматически inline", но в рамках ODR эффект действительно как от inline .
@Kaznachei: Если ваш Test.h много раз включится в main.cpp , то будет ошибка компиляции. Но такого быть не может - у вас include guards специально для этого стоят.
Есть один способ, но неудобный.
В test.cpp можно явно инстанцировать class Test для Type = int , добавив в конец файла:
Неудобство в том, что в .cpp файле вы должны перечислить все наборы шаблонных аргументов, с которыми планируете использовать класс.
Это - самое последнее средство. Намного лучше будет послушать @Harry и просто держать дефиниции шаблонных функций в заголовочных файлах.
А почему template без <> , просто я знаю, что можно писать template <> в определенных случаях, а вот без <> ? @HolyBlackCat
@Kaznachei "Дефиниция" значит "определение". Например, void f(); - объявление функции, а void f() <> - определение.
Во-первых, неработоспособность вашего подхода никак не связана с тем, что "компилятор не знает, как определить мои Type в Test.cpp". Вы продолжаете это повторять из вопроса в вопрос, но никакого отношения к сути проблемы это не имеет.
Во-вторых, возможность силой "заставить" что-то работать в таком варианте у вас есть: через явное инстанцирование ваших определений в .cpp файле для тех типов, с которыми ваш шаблон используется в программе. Но это в общем случае является нецелевым использованием возможности явного инстанцирования.
Вся суть шаблонов заключается в том, что компилятор должен сам автоматически определять, какие версии ваших шаблонных сущностей нужны в программе и генерировать их автоматически. Для этого нужно, чтобы все определения шаблонов выли видны везде, а не прятались в каком-то .cpp файле. Возможность явного инстанцирования существует в языке в первую очередь для оптимизации этого процесса, а не для того, чтобы заменять автоматическое инстанцирование на ручное.
Весь гугл перерыл, не могу понять. И википедию перечитал и вообще все что угодно перечитал. Правда не понимаю.
Что мешает подключать просто .cpp файлы?
Ну подключил ты его два раза, ну пусть компилятор только один раз подключает и все, раз он один черт все в один как бы файл склеивает, значит первое подключение будет выше остальных и будет видно им внизу. Пусть смотрит время изменения и перекомпилирует только изменившееся. Пусть автоматически генерирует и прикрепляет header файлы с описанием интерфейсов к откомпилированному бинарнику и т.п.
Иными словами, почему рутиная работа по генерации хедер файлов, не автоматизирована и возложена на разработчика? Ведь компилятор рядом с откомпилированным бинарником может легко сам сгенерировать файл описания интерфейсов (который он получил, сканируя cpp файл).
Можете привести ситуацию, в которой бы возникали ПРОБЛЕМЫ без использования хедер файлов? Это будет самое лучшее обьяснение.
Наверно уже сказали, но на всякий случай: Потому что можно писать без .h и можно просто включать .cpp Просто ты не понимаешь разницу между двумя подходами и не понимаешь поэтому, зачем это нужно - использовать заголовочные файлы.
Проблема лежит в области обратной совместимости.
Перенесёмся на полвека назад, в 1972 год. Представим себе компилятор языка C.
Допустим, мы хотим написать дизайн компилятора. Мы не можем скомпилировать всю программу за раз, у нас на это просто не хватит памяти. Компьютеры тогда были маленькими и медленными. Мы хотим компилировать программу по кусочкам, по нескольку функций за раз.
У нас сразу же возникает проблема: как скомпилировать функцию f , которая ссылается на другую функцию g ? Нам нужно отдельное описание других функций. Мы могли бы, конечно, прочитать все исходные файлы, для начала выяснить, какие функции у нас есть, и затем прочитать их второй раз и скомпилировать один за одним. Но это было слишком сложно и медленно, нужно было парсить определения функций дважды, и один раз выбрасывать результат! Это недопустимый расход процессорного времени! Плюс если держать в памяти определения всех функций, может снова-таки не хватить памяти.
На кого Деннис решил возложить сложную проблему отделения описания функции от её реализации, и подключения только нужных описаний при компиляции данной функции? На нас, программистов. Он решил, что мы должны сами помочь компилятору и скопипастить определения функций в отдельный файл, и сами указать компилятору, какие файлы с определениями нужны. (То есть первый шаг компиляции возлагается на нас.)
Это радикально упростило компилятор, но привело в свою очередь к проблемам. Что будет, если мы забыли подключить нужный заголовочный файл? Ответ: ошибка компиляции. Что будет, если смысл текста заголовочного файла меняется в зависимости от какого-нибудь макроса? Ответ: компилятор «тупой», и не пытается детектировать эту проблему, он перекладывает ответственность на нас.
На момент разработки языка это было правильное решение. Компилятор оказался практичным, быстрым, и программисты были не прочь помочь компиляции. Ну и если кто допускал ошибку, сам был виноват.
Перемотаем стрелки часов в 1983 год. Бьярн создаёт C++. Он решил взлететь на волне популярности языка C, и перенял модель компиляции C с отдельными translation unit'ами и связанными с этим проблемами прямо из C. Впрочем, первые версии C++ были просто препроцесоором языка C! Поэтому проблемы раздельной компиляции перекочевали из C в C++. Хуже того, добавились новые проблемы. Например, шаблоны классов выглядят как классы, но не генерируют объектного кода сами по себе, поэтому для них приходится идти на ухищрения и обходить недостатки системы раздельной компиляции (например, включением реализации в header и трюками компоновщика).
А дальше вступила в игру обратная совместимость. Сейчас, в 2017 году, есть так много кода, написанного в стиле «с заголовками», и так много кода исходит из различных тонкостей, связанных с этим, что менять парадигму уже поздно, поезд практически уехал.
Впрочем, существует проект модульной системы в C++, который должен помочь программистам избавиться от наследства полувековой давности. Он ещё не реализован, и в нём есть сложности уровня дизайна (например, в header'е был определён макрос, будет ли он виден, если мы перейдём от header'ов к модулям?) Надеюсь, в будущем разработчики языка таки смогут побороть проблему обратной совместимости.
Это наверное глупый вопрос, но я искал довольно долго здесь и в интернете и не мог придумать четкого ответа (сделал мою должную осмотрительность в гугле).
Итак, я новичок в программировании. Мой вопрос в том, как основная функция знает об определениях функций (реализациях) в другом файле?
ex. Скажем, у меня есть 3 файлы
- main.cpp
- myfunction в консоли.cpp
- myfunction в консоли.ГЭС
Я понимаю, как препроцессор включает код заголовка, но как заголовок и основная функция даже знают, что определение функции существует, а тем более используют его?
Я прошу прощения, если это не ясно, или я очень ошибаюсь в чем-то, новом здесь
заголовочном файле объявляет функции / классы-т. е. сообщает компилятору, когда он компилирует .cpp file какие функции / классы доступны.
на .cpp file определяет эти функции-т. е. компилятор компилирует код и поэтому создает фактический машинный код для выполнения тех действий, которые объявлены в соответствующем .
Итак, когда вы компилируете main.cpp в объектный файл (.o расширение) в этом файле отмечается, что для этого требуется функция myfunction . При компиляции myfunction.cpp в объектный файл, объектный файл имеет примечание в том, что ему есть определение myfunction .
тогда, когда вы приходите к связыванию двух объектных файлов вместе в исполняемый файл, компоновщик связывает концы - т. е. main.o использует myfunction как определено в myfunction.o .
надеюсь, это поможет
вы должны понимать, что компиляция-это 2-ступенчатая операция с точки зрения пользователя.
СОДЕРЖАНИЕ
Используя эту структуру, также создается файл определения класса, содержащий объявление класса и его членов. Если определение класса было включено , и файл реализация для его методов доступен, пользователь может создать экземпляр в объекте класса. Цель этой структуры - скрыть код реализации, но позволить пользователю просматривать дизайн. [2] [3]
Пользователи используют общедоступный интерфейс объекта, чтобы максимально упростить создание объектов, гарантируя, что клиентский код не отвлекает пользователя ненужными деталями реализации класса. [4] Это позволяет пользователю получить информацию, необходимую для эффективного использования класса, но не дает ему повредить скомпилированный код. [5]
Файл реализации используется в программировании на C ++ при создании определения класса для отделения интерфейса от реализации. Заголовочный файл будет объявить все функции - члены (методы) и методы передачи данных (поля) , что класс имеет. [6] [7] [8]
Файл реализации будет содержать фактическое определение или исходный код методов, объявленных в файле заголовка . Этот файл может начинаться с блока заголовка, который предоставляет комментарии, описывающие цель определенного класса и любые подробности о создании фактического файла, такие как автор файла и дата создания файла. [9] Он также может включать любые библиотеки из стандартной библиотеки C ++, которые будут использоваться любым из методов, объявленных в файле. В файле реализации класса обычно есть строка для включения связанного файла заголовка (см. Примеры ниже).
Примером может быть класс с именем ExampleClass . Заголовочный файл этого файла C ++ будет называться «example_class.h», а файл реализации - «example_class.cc». [10] [11]
Пример структуры example_class.cc будет выглядеть так:
В этом примере реализация функций опущена, но функции должны быть объявлены в example_class.h следующим образом: [12]
Другой пример того, как будет структурирован файл реализации класса, можно увидеть с помощью Objective-C , который используется в программировании iOS . [13] В этом примере будет использоваться ExampleClass. Заметное различие между C ++ и Objective-C при использовании этих файлов реализации - это расширения, используемые в конце файлов. В C ++ это будет .cpp [14], а в Objective-C - .m , [15], но оба будут использовать одно и то же расширение .h для своих файлов заголовков [16] [17], как показано в пример ниже.
Это пример ExampleClass.h в Objective-C :
Это пример файла реализации класса Exampleclass.m в Objective-C :
В чем разница между заголовочным и файлом реализации и какие существуют стандарты оформления C++ кода? Сделав C++ класс (не интерфейс) думаю неудобно открывать/создавать еще один файл и определять все. В чем плюсы такого подхода?
И хотя я и поставил минус за такой, с позволения сказать вопрос, попытюсь объяснить максимально просто: используйте h и hpp файлы ТОЛЬКО для объявления функций и классов. А для их определения используйте c и cpp файлы. И из этого, думаю понятно, что включения с помощью директивы include требуют ТОЛЬКО хэдэры (тобишь h и hpp файлы).
Это вполне нормальный вопрос, не за что ставить минус. Многие были новичками и не все внимательно читали книги. Напишите нормальный ответ - и уверен: ещё долго время будете собирать плюсы на такой ответ. И плюса вопрос недостоин, вот ответ если появится - то да.
@AndrejLevkovitch, это не всегда так, например minizip подключается .с файлом через инклюде, и таких примеров полно. Но сама идеология верна, хоть на практике и встречаютсяя различные варианты.
@AndrejLevkovitch, и тем не менее существует немало "библиотек", реализованных даже в одном .h файле. Я уже не говорю о всех библиотеках, использующих templates, где значительная часть рабочего кода классов и функций просто обязана быть в .h Так что, лучше отзовите свой минус
2-й шаг : Связывание
на этом шаге линкер взглянет на все эти индексы ваших объектных файлов и попытается решить зависимости в этих файлах. Если его там нет, вы получите знаменитый undefined symbol XXX от него. Затем он переведет эти ссылки в реальный адрес памяти в результирующем файле: двоичном или библиотеке.
и затем вы можете начать спрашивать, как это возможно сделать с гигантской программой, такой как офисный пакет, у которых есть тонны методов и объектов ? Ну, они используют общая библиотека механизм. Ты знаешь их своими ".dll ' и / или '.Итак, файлы, которые у вас есть на рабочей станции Unix/Windows. Это позволяет отложить решение неопределенного символа до запуска программы.
это даже позволяет решить неопределенный символ по требованию С dl* функции.
когда вы пишете:
здесь @call можно рассматривать как словарь look-up. А если задуматься об аналогии со словарем, то наверняка можно узнать о слове (smogashboard ?) прежде чем узнать его определение. Все, что вам нужно, это то, что во время выполнения определение будет в словаре.
как это @call работы ? Из-за Аби. ABI-это способ, который описывает многие вещи, и среди них, как выполнить вызов данной функции (в зависимости от ее параметров). Контракт вызова прост: он просто говорит, где можно найти каждый из аргументов функции (некоторые будут в регистрах процессора, некоторые другие в стеке).
поэтому @call фактически делает:
и функции определение знает, что его первый аргумент (x) находится в reg0 .
3. Но я думал, что словари были для динамических языков ?
и вы в какой-то степени правы. Динамические языки обычно реализуются с помощью хэш-таблицы для поиска символов, которая динамически заполняется.
для C++ компилятор преобразует единицу перевода (грубо говоря, предварительно обработанный исходный файл) в объект ( .o или .obj в общем). Каждый объект содержит таблицу символов, на которые он ссылается, но для которых это определение не известно:
затем компоновщик объединит объекты и согласует символы. В этой точке есть два вида символов:
- те, которые находятся в библиотеке и на которые можно ссылаться через смещение (окончательный адрес пока неизвестен)
- те, которые находятся вне библиотеки, и чей адрес полностью неизвестно до времени выполнения.
оба можно обработать в таком же образе.
и тогда код будет ссылаться на внешний вид записи:
при загрузке библиотеки ( DLL_Open например), среда выполнения, наконец, узнает здесь символ отображается в памяти и перезаписывает С реальным адресом (для этого запускаются).
Как указано в комментарии Матье м., Это линкер задание чтобы найти правильную "функцию" в нужном месте. Шаги компиляции, грубо говоря:
- компилятор вызывается для каждого файла cpp и переводит его в объектный файл (двоичный код) с таблица символов который связывает имя функции (имена искажаются в C++) к их местоположению в объектный файл.
- компоновщик вызывается только один раз: с каждым объектным файлом в параметр. Он будет разрешать местоположение вызова функции из одного объекта файл в другой благодаря таблицы символов. Одна функция main() должна существовать где-то. В конечном итоге создается двоичный исполняемый файл когда линкер нашел все, что ему нужно.
препроцессор включает содержимое заголовочных файлов в файлы cpp (файлы cpp называются единицей перевода). При компиляции кода каждая трансляционная единица отдельно проверяется на наличие семантических и синтаксических ошибок. Наличие определений функций в единицах перевода не рассматривается. .obj файлы создаются после компиляции.
на следующем шаге, когда файлы obj связаны. определение функций (функций-членов для классов), которые используется поиск и связывание происходит. Если функция не найдена, возникает ошибка компоновщика.
в вашем примере, если функция не была определена в myfunction.cpp, компиляция все равно будет продолжаться без проблем. На этапе связывания будет сообщена об ошибке.
int myfunction(int); является прототипом функции. Вы объявляете функцию с ней, чтобы компилятор знал, что вы вызываете эту функцию при записи myfunction(0); .
и как заголовок и основная функция даже знают, что определение функции существует?
Ну, это работа Линкер.
при компиляции программы препроцессор добавляет исходный код каждого файла заголовка в файл, который его включил. Компилятор компилирует каждый . Результатом является число .obj файлы.
После этого приходит компоновщик. Линкер берет все .obj файлы, начиная с главного файла, всякий раз, когда он находит ссылку, которая не имеет определения (например, переменную, функцию или класс), он пытается найти соответствующее определение в другом .obj файлы, созданные в этап компиляции или поставляется компоновщику в начале этапа компоновки.
Теперь, чтобы ответить на ваш вопрос: каждый .cpp файл компилируется в .obj файл, содержащий инструкции в машинном коде. Когда вы включаете .hpp файл и использовать некоторую функцию, определенную в другом .cpp файл, на этапе связывания компоновщик ищет это определение функции в соответствующем . Вот как он это находит.
1-й шаг: компиляция объектов
во время этого шага, ваш *.файлы c индивидуально составлен в отдельные объектные файлы. Это значит, что когда main.cpp компилируется, он ничего не знает о вашем myfunction в консоли.cpp. Единственное, что он знает, это то, что ты объявить что функция с этой сигнатурой:.
компилятор сохранит ссылку на этот вызов и включит его непосредственно в объектный файл. Объектный файл будет содержать "я должен позвонить myfunction в консоли С int и он вернется ко мне с int. Он сохраняет индекс all extern звонков для того, чтобы иметь возможность связаться с другими потом.
2 ответа 2
Заголовочные файлы с расширением .inl обычно отличаются от других заголовочных файлов тем, что они не содержат include guard и их содержимое может многократно встречаться в тексте единицы трансляции.
Как тут уже правильно сказали, разница в этих файлах весьма условная. Исторически в языке Си в .h файлах хранились декларации, а в .c файлах хранились реализации.
Декларации это то, что используется в более чем одном месте программы. Исторически в языке Си это были дефайны, прототипы функций, описания структур, декларации typedef.
Реализации это то, что должно быть только в одном месте программы. Исторически в языке Си это были реализации функций и статические данные.
В языке С++ это деление сохранилось с учетом того, что в .h файлах теперь содержатся описания классов, шаблонов, констант и опять же всего того, что используется в более чем одном месте программы.
Также с появлением шаблонов в языке С++ наметилась тенденция (особенно в построении библиотек шаблонов), что вся программа содержится в .h файлах.
Вообще весь язык Си был сделан Ричи и Томсоном "для себя", как инструмент при написании первых версий Юникса. Поэтому этот язык содержит так много элементов, которые поддерживают разработку программ при помощи транслятора с языка высокого уровня. Разделение программы на .h и .c/.cpp файлы как раз и относится к такой поддержке и это разделение вытекает из логики деления проекта на разные единицы трансляции то есть на разные файлы.
Например у меня есть Test.h в котором определен шаблон класса Test и в Test.cpp определены методы и конструкторы класса причем неявным образом,а вот уже где нибудь в main.cpp я инициализирую Test testValue . Ничего не сработает, так как компилятор не знает, как определить мои Type в Test.cpp . Как можно "схитрить" ( Не используя дополнительного третьего файла типа .inl или что нибудь еще)
Test.h
Test.cpp
main.cpp
Читайте также: