如果库的包含路径设置不符合预期,如何引发编译错误

How to throw a compile error if the include paths for a library were set up not as intended

本文关键字:何引发 错误 编译 不符合 包含 路径 设置 如果      更新时间:2023-10-16

我们的C++库包含一个文件,其名称(被认为)等于标准库的一个标头。在我们的例子中,这是"String.h",Windows认为它与"String.h"相同,但为了这个问题,它可以是标准库中使用的任何其他文件名。

通常情况下,这种文件名模糊性不是问题,因为用户应该设置包含路径,只包括库文件夹的父文件夹(因此需要包括"LibraryFolder/String.h"),而不包括包含标头的文件夹。

但是,有时用户会错误地将include路径直接设置为包含文件夹。这意味着"String.h"将代替"String.h"包含在用户代码和标准库头中,从而导致许多编译错误,初学者可能不容易解决或理解这些错误。

在编译期间,是否有可能在我们库的头中检测到这种错误设置的包含路径,并根据对包含路径的某种检查,通过指令立即抛出编译#警告或#错误?

没有故障保护的方法。如果编译器找到另一个文件,它不会抱怨。

然而,你可以制作它,这样你就可以检测到它。在你自己的LibraryName/string.h中,你可以定义一个唯一的符号,比如

#define MY_STRING_H412a55af_7643_4bd6_be5c_4315d3a1e6b7

然后稍后在依赖代码中,您可以检查

#ifndef MY_STRING_H412a55af_7643_4bd6_be5c_4315d3a1e6b7
#error "Custom standard library path not configured correctly"
#endif

同样,您可以使用它来检测何时包含了错误版本的库。

[编辑-根据注释]标题包含可以概括为:

  • 分析#include行以确定要查找的标头名称
  • 根据<Foo.h>"Foo.h"形式,确定要搜索的位置集(通常是目录)
  • 以依赖于实现的方式解释标头名称。(通常作为相对路径)。请注意,这不一定是字符串,例如MSVC不将视为字符串转义符
  • 如果找到了标头(通常,如果找到了文件),请将#include行替换为该文件的内容。否则,编译失败

(括号中的"通常"适用于MSVC、GCC、clang等,但理论上编译器可以直接从git存储库而不是磁盘文件进行编译)

这里的问题是,想象中的测试(头名称的拼写)必须位于包含的头文件中。该测试必然是被替换的#include线路的一部分,因此不再存在,无法进行测试。

C++17引入了__has_include,但这并不影响分析:它仍然必须出现在包含的头文件中,并且#include "Foo.h"中的字符序列不可用。

[旧]可能最简单的方法,尤其是对于初学者来说,就是有一个LibraryName/LibraryName.h。希望这个名字是独一无二的。

这样做的好处是,一旦成功,用户就可以将#include "LibraryName.h"替换为#include "String.h",因为您知道路径是正确的。

也就是说,"String.h"是在问问题。Windows不区分大小写。

使用namespaces。在您的情况下,这将转化为以下内容:

MyString/String.h
namespace my_namespace {
class string {
...
}
}

现在,为了确保您的std::string或任何其他名为string的类没有被意外地而不是my_namespace::string使用(通过任何方式,包括但不限于错误地设置包含路径),您需要使用其完全限定名称(即my_namespace::string)来引用您的类型。通过这样做,可以避免任何命名冲突,并且如果不包含正确的头文件(除非实际上存在另一个名为my_namespace::string的类,而该类不是您的),则可以保证会出现编译错误。还有其他方法可以避免这些冲突(例如using my_namespace::string),但我更愿意明确我正在使用的类型。然而,这种解决方案成本高昂,因为它可能需要对整个代码库进行更改(将所有strings更改为my_namespace::string)。

稍微不那么麻烦的替代方案是将报头String.h的名称更改为类似MyString.h的名称。这将很快引入编译错误,但需要更改#include "String.h" into#include"MyString.h"`中的所有include(与第一个选项相比,应该工作量要小得多)。

到目前为止,我想不出任何其他方式需要更少的努力。由于您正在寻找一种适用于所有类似场景的解决方案,如果我是您,我会选择namespace,并一劳永逸地解决问题。这将防止代码中可能存在的任何其他现有/将来的命名冲突。