在 C++ 中,函数、变量和常量的声明和定义可以像这样分开:

function someFunc();

function someFunc()
{
  //Implementation.
}

事实上,在类的定义中,经常会出现这种情况。类通常在 .h 文件中声明其成员,然后在相应的 .C 文件中定义这些成员。

这种方法的优点和缺点是什么?

有帮助吗?

解决方案

历史上,这是为了帮助编译器。你必须给它的名字的列表它曾经在他们面前 - 这是否实际使用,或预先声明(C默认funcion原型除外)

有关现代语言现代编译显示,这不再是必要的,所以C&C ++的(以及Objective-C的,而且很可能其他人)在这里的语法是histotical行李。实际上一个,这是与C ++,即使在添加适当的模块系统的将不解决的大问题之一。

缺点是:大量的重嵌套的包含文件(我已经追踪包括树之前,他们有着惊人的庞大),并声明和定义之间的冗余 - 所有这些都导致较长的编码次数和更长的编译时间(以往相比,之间的编译时间可比C ++和C#项目?这其中的差异的原因)之一。头文件必须为你提供的任何组件的用户。 ODR侵犯的机会。预处理器的依赖(许多现代语言不需要预处理器步骤),这使你的代码更脆弱,更难以工具来解析。

优点:没有太大。你可以说,你在一个地方文件的目的聚集在一起的功能名称的列表 - 但大多数IDE有某种代码折叠能力,这些天,任何规模的项目应采用DOC发生器(如doxygen的)反正。有了一个更清洁,预处理器少,基于模块的语法它是工具,按照你的代码,并提供了这一点,并更容易,所以我觉得这个“优势”是只是没有实际意义。

其他提示

它是 C/C++ 编译器工作原理的产物。

当源文件被编译时,预处理器用包含文件的内容替换每个 #include 语句。只有之后编译器才会尝试解释此串联的结果。

然后编译器从头到尾检查该结果,尝试验证每个语句。如果一行代码调用了之前未定义的函数,它将放弃。

但是,当涉及到相互递归函数调用时,存在一个问题:

void foo()
{
  bar();
}

void bar()
{
  foo();
}

这里, foo 不会编译为 bar 未知。如果你把这两个功能调换一下, bar 不会编译为 foo 未知。

但是,如果将声明和定义分开,则可以根据需要对函数进行排序:

void foo();
void bar();

void foo()
{
  bar();
}

void bar()
{
  foo();
}

这里,当编译器处理 foo 它已经知道一个名为的函数的签名 bar, ,并且很高兴。

当然,编译器可以以不同的方式工作,但这就是它们在 C、C++ 以及某种程度上 Objective-C 中的工作方式。

缺点:

没有直接的。如果您无论如何都在使用 C/C++,那么这是最好的方法。如果您可以选择语言/编译器,那么也许您可以选择一个不存在问题的语言/编译器。将声明拆分到头文件中唯一需要考虑的事情是避免相互递归的 #include 语句 - 但这就是包含防护的用途。

优点:

  • 编译速度:由于所有包含的文件都是串联然后解析的,因此减少了包含文件中的代码量和复杂性 将要 改进编译时间。
  • 避免代码重复/内联:如果您在头文件中完全定义函数,则包含此头文件并引用此函数的每个对象文件将包含该函数自己的版本。作为旁注,如果您 内联,您需要将完整的定义放入头文件中(在大多数编译器上)。
  • 封装/清晰度:定义良好的类/函数集加上一些文档应该足以让其他开发人员使用您的代码。(理想情况下)他们不需要了解代码是如何工作的 - 那么为什么要求他们筛选代码呢?(反驳认为,访问实施可能对他们有用 在需要的时候 当然,仍然存在)。

当然,如果您对公开函数根本不感兴趣,通常仍然可以选择在实现文件而不是标头中完全定义它。

有对分离声明和定义成C ++头文件和源文件的两个主要优点。第一个是你避免当你的类/功能/不管是一个定义规则问题#included在多个地方。其次,通过这样做,让你分开接口和实现。你的类或库的用户只需要看到你的头文件,以编写使用它的代码。您也可以借此一步与 PIMPL方法并让这个用户代码不必重新编译每次库实现的变化。

您已经提到的.h和.cpp文件之间的代码重复的缺点。也许我已经写C ++代码太长,但我不认为这是的的坏。您在每次更改函数签名反正时间来改变所有用户代码,所以什么是多了一个文件?这只是恼人的,当你第一次写一个类,你必须从标题到新的源文件进行复制和粘贴。

在实践中的另一个缺点是,为了写(和调试!)使用第三方库好的代码,你通常看到里面。这意味着访问源代码,即使你不能改变它。如果你已经是一个头文件和编译的目标文件,它可以是非常困难的决定,如果错误是你的错,还是他们。另外,查看源让您深入了解如何正确使用和扩展该文档可能不包括图书馆。不是每个人都附带了自己的图书馆的MSDN。和伟大的软件工程师与您的代码,你从来没有想到过的做事情的坏习惯。 ; - )

该标准要求使用功能时,一个声明必须是在范围内。这意味着,编译器应该能够验证对您传递给它什么样的原型(在头文件中的声明)。除了当然,对于那些可变参数函数 - 这样的功能不验证参数

认为C,当这不是必需的。当时,编译器处理不被默认为int返回类型规范。现在,假设你有一个函数foo(),它返回一个指针无效。不过,既然你没有一个声明,编译器会认为它必须返回一个整数。在摩托罗拉的一些系统,例如,integeres和指针将被在不同的寄存器返回。现在,编译器将不再使用正确的寄存器,而是返回指针强制转换为整数中的其他寄存器。那一刻你尝试用这个指针的工作 - 所有的地狱破散

报头内声明功能是好的。但要记住,如果你申报,并在标题定义确保它们是内联。实现这一目标的方法之一是把定义在类定义内。否则在前面加上inline关键字。你会遇到的ODR违反,否则当标题中包含多个实现文件。

您基本上对类/函数2次/不管:

声明,在此声明的名称,参数和部件(在struct /类的情况下),以及定义在其中定义的功能做什么。

当中的缺点是重复的,但一个很大的好处是,你可以声明功能int foo(float f)并在实施退出细节(=定义),所以任何人谁愿意使用你的函数foo只包括你的头文件和链接你的图书馆/ objectfile,使图书馆用户以及编译器只需要关心定义的接口,这有助于理解接口和速度高达编译时间。

一个优点,我还没有看到:的 API

任何文库或第三方的代码不是开源(即专有)将不具有其执行与分配一起。大多数公司都只是普通的不舒服放弃的源代码。最简单的解决方案,只需分发类声明和函数签名,其允许使用DLL。

免责声明:我不是说不管它是正确的,错误的,还是有道理的,我只是说我已经看到了很多

<强>优势

类可以从其他文件通过只包括声明被引用。定义然后可以在编译过程中稍后的联系。

向前声明的一个大优点是使用时要小心,你可以减少模块之间的编译时依赖关系。

如果ClassA.h需要引用的数据元素中ClassB.h,通常可以使用刚刚在ClassA.h正向引用和包括在ClassB.h ClassA.cc而非ClassA.h,从而减少了一个编译时间依赖性。

有关大系统,这可以在一个构建节省大量的时间。

  1. 分离提供了程序元素的干净、整洁的视图。
  2. 可以在不公开源的情况下创建和链接到二进制模块/库。
  3. 链接二进制文件而无需重新编译源。

当正确完成,这种分离减小当仅实现已更改的编译时间。

<强>缺点

这导致了大量的重复。最函数签名的需要被放置在两个或多个(如Paulious说明)的地方。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top