【问题标题】:Why create an include/ directory in C and C++ projects?为什么要在 C 和 C++ 项目中创建 include/ 目录?
【发布时间】:2018-07-11 07:21:14
【问题描述】:

当我处理我的个人 C 和 C++ 项目时,我通常将 file.hfile.cpp 放在同一个目录中,然后 file.cpp 可以使用 #include "file.h" 指令引用 file.h

但是,通常会发现库和其他类型的项目(如 linux 内核和 freeRTOS),其中所有.h 文件都放在include/ 目录中,而.cpp 文件保留在另一个目录中。在这些项目中,.h 文件也包含在 #include "file.h" 中,而不是我希望的 #include "include/file.h"

我对这一切有一些疑问:

  1. 这种文件结构组织有什么优势?
  2. 为什么include/ 中的.h 文件包含在#include "file.h" 中,而不是#include "include/file.h"?我知道真正的诀窍是在某个 Makefile 中,但这样做真的比(在代码中)明确我们要包含的文件实际上在 include/ 目录中更好吗?

【问题讨论】:

  • 如此组织时,include 是包含的根,因此它不是实际包含名称的一部分。通常 - 但并非总是如此 - 您还会在 src 目录中拥有源文件,顺便说一句。
  • 这种方式更灵活,你可以改变头文件的位置,只需要在一个地方更新这个事实,想象一下必须检查内核中所有使用某些文件的文件是多么疯狂库,如果您决定更改目录结构并且必须一一修改包含。这也节省了大量的打字时间。
  • 你能想象如果必须在不同的目录中找到每个系统头文件会是什么样子吗?混乱并没有开始描述它。这就是为什么它们被收集到一个或极少数目录中的原因。

标签: c++ c include


【解决方案1】:

这样做的主要原因是编译的库需要标头才能被最终用户使用。按照惯例,include 目录的内容是公开供公众使用的标头。源目录可能有供内部使用的标头,但这些标头要与已编译的库一起分发。

所以在使用库时,您需要链接到二进制文件并将库的include 目录添加到构建系统的头文件路径中。同样,如果您将编译后的库安装到集中位置,您可以知道哪些文件需要复制到中心位置(编译后的二进制文件和include 目录),哪些文件不需要(源目录等) )。

【讨论】:

  • 补充...这是一种为您提供模块化和按合同设计的方式。通常包含目录中的文件也将包含记录库行为的 cmets(Doxygen 很受欢迎)。 C# 有部分类定义的概念,这使得隐藏实现更容易,但您仍然需要决定什么应该是外部接口,什么应该是库内部的。
【解决方案2】:

以前<header> 样式包含属于隐式路径类型,即在includes 环境变量路径或构建宏中找到,而"header" 样式包含属于显式形式,原样,完全相对于包含它的源文件的任何位置。虽然一些构建工具链仍然允许这种区别,但它们通常默认为有效地取消它的配置。

您的问题很有趣,因为它提出了一个问题,即隐含或显式哪个更好?隐式形式当然更容易,因为:

  1. 在目录层次结构中方便地对相关标题进行分组。
  2. 您只需要在includes 路径中包含几个目录,无需了解有关文件确切位置的每一个细节。您可以在不更改代码的情况下更改库的版本及其相关标头。
  3. DRY
  4. 灵活!您的构建环境不必与我的匹配,但我们通常可以得到几乎完全相同的结果。

另一方面,显式有:

  1. 可重复构建。 includes 宏/环境变量中的路径重新排序不会更改生成期间找到的头文件。
  2. 便携式构建。只需从构建的根目录打包所有内容,然后将其发送给另一个开发人员。
  3. 信息接近。您确切地知道 #include "\X\Y\Z" 的标题在哪里。在隐式形式中,您可能必须沿着多个路径进行搜索,甚至可能会找到同一文件的多个版本,您如何知道构建中使用了哪个版本?

几十年来,构建者一直在为这两种方法争论不休,但两者的混合形式大多胜出,因为维护纯粹基于显式形式的构建需要付出努力,而且人们可能很难熟悉自己的方法self 具有纯隐式性质的代码。我们都普遍理解我们的各种工具链将某些通用库和头文件放在特定位置,以便可以在用户和项目之间共享它们,因此我们希望在一个地方找到标准 C/C++ 头文件,但我们最初并没有对任意项目的具体结构一无所知,缺乏本地记录良好的约定,因此我们希望这些项目中的代码对于它们独有的非标准位是明确的,而对于标准位是隐含的。

对于所有标准头文件和其他非项目特定的库,始终使用 <header> 形式的 include 并为其他所有内容使用 "header" 形式是一种很好的做法。您的项目中是否应该有一个包含目录用于本地包含?这在一定程度上取决于这些标头是作为库的接口提供还是仅由您的代码使用,还取决于您的偏好。您的项目有多大和多复杂?如果您混合了内部和外部接口或许多不同的组件,您可能希望将事物分组到单独的目录中。

请记住,您的成品解压到的目录结构与您在其中开发和构建该产品的目录结构没有任何相似之处。如果您只有几个 .c/.cpp 文件和头文件,它是可以将它们全部放在一个目录中,但最终,您将要做一些不平凡的事情,并且必须考虑构建环境选择的后果,并希望将其记录下来以供其他人理解。

【讨论】:

  • 我认为这是一个非常全面的答案。我要补充一点,我们经常混合使用——我们的库的所有需要​​外部链接的头文件(它们实际提供的)都分组在头文件目录中——因此它们可以作为一个块轻松移动——这通常是“安装”。仅在内部需要的标头明确链接到并与源一起保存,以保持源的可移植性并将信息分组。
  • 答案很好,尽管对我来说,“显式与隐式”不如“硬编码与可配置”那么多。例如,explicit.1 无效,因为没有一个严肃的项目没有构建系统,然后自然而然地让构建系统处理路径。与explicit.2相同,您不会只打包源代码,而是打包整个文件,标题,src,数据,构建设置,因此标题是否在包中的另一个目录中并不重要。
【解决方案3】:

1 . .hpp和.cpp不一定是1对1的关系,根据不同的条件(例如:不同的环境),可能有多个.cpp使用同一个.hpp,例如:一个多平台库,想象有一个类获取app的版本,header是这样的:

实用程序.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 for iOS(路径:DemoProject/ios/Utilities.cpp):

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

.cpp for 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" 

当您的标头不再放在“include”文件夹中时,允许您保持源代码不变。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-04
    • 2016-03-30
    • 1970-01-01
    • 2015-09-05
    • 2012-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多