Зачем создавать каталог include/в проектах C и C++?

Когда я работаю над своими личными проектами C и C++, я обычно помещаю file.h и file.cpp в тот же каталог, а затем file.cpp может ссылаться на file.h с директивой #include "file.h".

Однако обычно обнаруживают библиотеки и другие проекты (например, linux kernel и freeRTOS), где все файлы .h находятся внутри каталога include/, а файлы .cpp остаются в другом каталоге. В этих проектах файлы .h также включаются в #include "file.h" вместо #include "include/file.h" как я надеялся.

У меня есть некоторые вопросы обо всем этом:

  1. Каковы преимущества этой организации файловой структуры?
  2. Почему файлы .h внутри include/ включены с #include "file.h" вместо #include "include/file.h"? Я знаю, что настоящий трюк находится внутри какого-либо Makefile, но лучше ли это делать, а не делать ясным (в коде), что файл, который мы хотим включить, действительно находится в каталоге include/?

Ответ 1

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

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

Ответ 2

Раньше считалось, что <header> стиль включает в себя были неявного типа пути, то есть, можно найти на includes переменную окружения путь или сборки макрос, а "header" стиль включает в себя были в явном виде, как и в, точно по отношению к где-либо исходный файл содержит его. Хотя некоторые цепочки инструментов построения все еще допускают это различие, они часто по умолчанию используют конфигурацию, которая фактически сводит на нет ее.

Ваш вопрос интересен, потому что он поднимает вопрос о том, что действительно лучше, неявно или явно? Неявная форма, конечно, проще, потому что:

  1. Удобные группировки связанных заголовков в иерархиях каталогов.
  2. Вам нужно всего лишь включить несколько каталогов в includes в includes путь и не должны знать о каждой детали относительно точного местоположения файлов. Вы можете изменять версии библиотек и связанных с ними заголовков без изменения кода.
  3. СУХОЙ.
  4. Гибкая! Ваша среда сборки не должна совпадать с моей, но мы часто получаем почти точные результаты.

С другой стороны, явный:

  1. Повторяющиеся сборки. Переупорядочение путей в переменной includes макро/среду, не изменяет итоговые файлы заголовков, найденные во время сборки.
  2. Портативные сборки. Просто упакуйте все из корня сборки и отправьте его другому разработчику.
  3. Близость информации. Вы точно знаете, где заголовок содержит #include "\X\Y\Z". В неявной форме вам, возможно, придется искать по нескольким путям и даже найти несколько версий одного и того же файла, как вы знаете, какой из них используется в сборке?

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

Рекомендуется использовать форму <header> include для всех стандартных заголовков и других библиотек, которые не являются специфичными для проекта, и использовать форму "header" для всего остального. Если у вас есть каталог include в вашем проекте для вашего локального? В какой-то степени это зависит от того, будут ли эти заголовки отправляться как интерфейсы к вашим библиотекам или просто использованы вашим кодом, а также ваши предпочтения. Насколько большой и сложный ваш проект? Если у вас есть сочетание внутренних и внешних интерфейсов или множества разных компонентов, вы можете группировать вещи в отдельные каталоги.

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

Ответ 3

1. .hpp и.cpp не обязательно имеют отношение 1 к 1, может быть несколько.cpp, используя тот же.hpp в соответствии с разными условиями (например, разные среды), например: многоплатформенная библиотека, представьте, что существует класс чтобы получить версию приложения, и заголовок выглядит следующим образом:

Utilities.h

#include <string.h>
class Utilities{
    static std::string getAppVersion();
}

main.cpp

#include Utilities.h
int main(){
    std::cout << Utilities::getAppVersion() << std::ends;
    return 0;
}

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

.cpp для iOS (путь: DemoProject/ios/Utilities.cpp):

#include "Utilities.h"
std::string Utilities::getAppVersion(){
    //some objective C code
}

.cpp для Android (путь: DemoProject/android/Utilities.cpp):

#include "Utilities.h"
std::string Utilities::getAppVersion(){
    //some jni code
}

и, конечно, 2.cpp не будут использоваться в то же время в обычном режиме.


2.

#include "file.h" 

вместо

#include "include/file.h" 

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