在C++中,头文件和实现文件中的'#include'和'using'语句应该重复吗?

12
我对C ++还不太熟悉,但我理解 #include 语句基本上只会把 #included 文件的内容倾入到该语句的位置。这意味着,如果我在头文件中有一些 '#include' 和 'using' 语句,我的实现文件可以只 #include 头文件,编译器也不介意我是否重复其他语句。

那人怎么办呢?

我的主要担心是,如果我不重复 '#include'、'using',以及现在想起来的 'typedef' 语句,它会将在使用该文件的地方的信息带走,这可能会导致混乱。

我目前只在小项目上工作,所以这不会引起任何问题。但是,在更多人参与的大型项目中可能会成为一个重要问题。

以下是一个示例:

更新:我的“Unit”函数原型包含了字符串、输出流和 StringSet 作为它们的返回类型和参数 - 我没有在头文件中包含任何只在实现文件中使用的内容。

//Unit.h

#include <string>
#include <ostream>
#include "StringSet.h"

using std::string;
using std::ostream;

class Unit {

public:
    //public members with string, ostream and StringSet
    //in their return values/parameter lists
private:
    //private members
    //unrelated side-question: should private members
    //even be included in the header file?
} ;


//Unit.cpp

#include "Unit.h"

//The following are all redundant from a compiler perspective:
#include <string>
#include <ostream>
#include "StringSet.h"

using std::string;
using std::ostream;

//implementation goes here

我不想在主要问题中混淆的一个无关问题是,既然每个人似乎都说全局的 'using' 语句是个坏主意,那么我怎样才能只使用类 'using std::string' 或者类似的东西?把该语句移到类声明中会导致编译器错误,因为它不喜欢在该位置使用命名空间中的 'using' 语句。 - David Mason
2
明确地说“std::string”有什么问题吗? - Travis Gockel
直到它开始导致行断裂并降低可读性,否则没有什么特别的。不过让我们暂时远离std::,假设我想要利用TheLongestNamespaceNameInTheKnownUniverse::string——只是为了避免常见的“你应该总是键入std::string”的说辞(因为我已经听过很多次了,虽然这是一个有效的观点,但它并没有真正帮助解决问题)。 - David Mason
1
我认为C++的做法不是使用长命名空间的原因 :) 我只在头文件中使用完全限定名称,并在cpp文件中使用'using'指令,我还没有找到更好的方法。我将大多数函数定义保留在cpp文件中,这有助于减少完全限定名称和头文件中包含的数量。 - Alex Korban
5个回答

11

如果不在函数内,using namespace std;这样的using-directive应该避免写在头文件中。这是不好的实践方式,不太可能每个使用你头文件的用户都希望对给定命名空间中的所有内容进行非限定名称查找;包含无关头文件可能会导致意外的二义性和编译错误。个人而言,我出于同样的原因避免在函数内使用using-directive,但这通常被认为是相对无害的。

类型别名(通过typedef std::string string;using string = std::string;)应谨慎使用。类型定义具有含义,因此您永远不应该重新声明它。例如,以下代码是错误的:

typedef int   myint;
typedef float myint;

由于类型冲突而出现的错误。

using-declaration(如using std::string;using std::memcpy;)使符号可用于未限定名称查找。当进行参数相关查找时,它非常有用,但通常并不重要,除非你正在编写库。建议取决于你是否引入了类型或函数。将使用类型的using-declaration视为类型别名:在相同名称下具有多个定义是没有意义的。对于函数,你真正做的只是扩展重载解析,以包括更多内容(尽管通常并不需要)。

// Finding multiple operator<< functions makes sense
using std::operator<<;
using mylib::operator<<;

// Finding multiple string classes does not make sense
using std::string;
using mylib::string;

对于重复的#include,你应该考虑是否真的需要首先在头文件中包含该文件。也许前向声明更适合您的需求。


我更新了问题以澄清:我避免在头文件中包含除了必须的之外的任何#includes。如果我正在使用std::string或其他未定义的类,我不确定该前向声明示例是否适用-您能说明一下在这种情况下该怎么做吗? - David Mason
std::string 的问题在于它实际上只是 typedef basic_string<char> string ... 标准规定:除非另有规定,否则 C++ 程序不能向命名空间 std 或命名空间 std 中的命名空间添加声明或定义。 因此...你不能前向声明它。至于 really_long_namespace_name 问题,这就是人们使用成员类型的地方。std::allocator 是一个很好的例子:http://www.cplusplus.com/reference/std/memory/allocator/ - Travis Gockel
在C++11中,using也可以用于类型别名声明,根据标准,这等同于typedef。由于头文件中的typedef是可以接受的,因此句子“除非它包含在函数内部,否则您不应该在头文件中使用using语句”不再正确。 - Zitrax
@Zitrax:同意。我认为C++11(及以后版本)的建议应该更具体:“您永远不应该有一个using namespace指令……”再次阅读我的建议后,它似乎有点愚蠢/误导。为什么我认为使用声明与typedef有所不同呢? - Travis Gockel
@Zitrax:在编辑过程中,我意识到大部分内容仍适用于C++11之前的版本,因此我重写了它以适用于所有人。 - Travis Gockel

7
  • 只在头文件/源文件中包含必要的内容(如果有前向声明,且足够,则使用前向声明而不是包含)。
  • 不要在头文件中使用using语句(除非在函数作用域内)……在头文件中添加using将污染所有包含该头文件的命名空间。
  • 您应确保每个文件(头文件或源文件)都包含它需要的一切,而没有多余的东西。

您不需要担心一些包含文件是否冗余。头文件保护和预编译优化可以处理这些问题。

您应该能够独立操作每个文件。

例如,假设您在头文件和源文件中都使用了std::string,但是出于“优化”的目的,您只在头文件中包含了string……如果您后来发现不再需要头文件中的string,并希望删除它(清理代码等等),那么您将不得不修改源文件以包含string。现在,让我们想象您有十个包含该头文件的源文件……

当然,您可以对此规则进行例外处理(例如,预编译头文件,或仅旨在作为礼貌多重包含的头文件),但默认情况下,您应该拥有自给自足的头文件和源文件(即文件包含所有它们使用的内容,没有多余的东西)。


0
保持头文件最小化。这意味着尽可能少的包含。.cpp文件通常会包括相应的头文件以及实现所需的任何其他头文件。

0

像Travis所说,你不应该在头文件中使用using语句,因为这意味着它们将被包含在所有包含该头文件的翻译单元中,这可能会导致混淆问题。

如果我只在cpp文件中需要头文件的功能,我只在该cpp文件中包含它。对于较大的项目来说,这是一个好的实践,因为它意味着编译器的工作更少。此外,在头文件中尽可能使用前向声明而不是包含(并再次在cpp文件中包含头文件)。


0

在头文件中使用using语句通常被认为是不好的做法,除非你有意将符号复制到不同的命名空间中。在cpp文件中使用是可以的。

每个typedef在代码库中应该只存在一次。如果需要在多个cpp/h文件中使用,则应该放在头文件中。重复定义会给你带来很多麻烦。

头文件应该包含它所需的所有#include语句,而且不应该包含其他的。如果只提到了类的指针,则应该使用前向声明而不是包含头文件。任何其他仅在cpp文件中需要的包含都应该放在那里。重复头文件中的包含是可以的,但不是必须的。这只是一个风格选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接