74

许多语言,例如 Java、C#,并没有将声明与实现分开。C# 有一个分部类的概念,但实现和声明仍然保留在同一个文件中。

为什么 C++ 没有相同的模型?有头文件更实用吗?

我指的是当前和即将发布的 C++ 标准版本。

4

17 回答 17

81

向后兼容性- 头文件不会被消除,因为它会破坏向后兼容性。

于 2009-04-15T17:23:17.850 回答
34

头文件允许独立编译。您不需要访问甚至不需要实现文件来编译文件。这可以使分布式构建更容易。

这也使 SDK 更容易完成。您可以只提供标题和一些库。当然,还有其他语言使用的方法。

于 2009-04-15T17:23:39.427 回答
32

甚至Bjarne Stroustrup也将头文件称为杂物。

但是如果没有包含必要元数据(如 Java 类文件或 .Net PE 文件)的标准二进制格式,我看不到任何实现该功能的方法。剥离的 ELF 或 a.out 二进制文件没有您需要提取的大量信息。而且我认为这些信息从未存储在 Windows XCOFF 文件中。

于 2009-04-15T19:17:05.177 回答
23

我经常在 C# 和 C++ 之间切换,而 C# 中缺少头文件是我最大的烦恼之一。我可以查看一个头文件并了解我需要了解的关于一个类的所有信息——它的成员函数被称为什么、它们的调用语法等——而无需浏览实现该类的代码页面。

是的,我知道部分类和#regions,但它不一样。部分类实际上使问题变得更糟,因为类定义分布在多个文件中。就#regions 而言,它们似乎从未以我目前正在做的事情的方式扩展,所以我必须花时间扩展这些小优点,直到我得到正确的视图。

也许如果 Visual Studio 的智能感知对 C++ 工作得更好,我就没有令人信服的理由不得不如此频繁地引用 .h 文件,但即使在 VS2008 中,C++ 的智能感知也无法触及 C#

于 2009-04-15T17:37:17.270 回答
17

C 旨在使编写编译器变得容易。它基于这一原则做了很多事情。指针的存在只是为了使编写编译器更容易,头文件也是如此。继承到 C++ 的许多东西都是基于与这些特性的兼容性,这些特性使编译器编写更容易。

这其实是个好主意。在创建 C 时,C 和 Unix 是一对。C 移植 Unix,Unix 运行 C。这样,C 和 Unix 可以迅速从一个平台传播到另一个平台,而基于汇编的操作系统必须完全重写才能移植。

在一个文件中指定接口并在另一个文件中指定实现的概念根本不是一个坏主意,但这不是 C 头文件的含义。它们只是一种限制编译器必须通过您的源代码进行的传递次数的方法,并允许对文件之间的契约进行一些有限的抽象,以便它们可以通信。

这些项目、指针、头文件等......实际上并没有比其他系统提供任何优势。通过在编译器上投入更多精力,您可以像编译指向完全相同的对象代码的指针一样轻松地编译引用对象。这就是 C++ 现在所做的。

C 是一种伟大而简单的语言。它的功能集非常有限,您可以轻松编写编译器。移植它通常是微不足道的!我并不是要说它是一种糟糕的语言或其他任何东西,只是 C 在创建它时的主要目标可能会在语言中留下一些现在或多或少不必要的残余物,但为了兼容性将被保留。


似乎有些人并不真正相信 C 是为移植 Unix 而编写的,所以在这里:(来自

UNIX 的第一个版本是用汇编语言编写的,但 Thompson 的意图是它将用高级语言编写。

Thompson 于 1971 年首次尝试在 PDP-7 上使用 Fortran,但第一天就放弃了。然后他写了一种非常简单的语言,他称之为 B,并在 PDP-7 上继续使用。它奏效了,但也出现了问题。首先,由于实现是被解释的,它总是会很慢。其次,基于面向字的 BCPL 的 B 的基本概念不适用于新的 PDP-11 等面向字节的机器。

Ritchie 使用 PDP-11 为 B 添加类型,有一段时间被称为 NB 的“New B”,然后他开始为它编写编译器。“因此,C 的第一阶段实际上是这两个阶段,首先是从 B 的一些语言更改,实际上是在语法上没有太大变化的情况下添加类型结构;然后进行编译器,”Ritchie 说。

“第二阶段较慢,”他谈到用 C 重写 UNIX 时说。Thompson 于 1972 年夏天开始,但有两个问题:弄清楚如何运行基本的协同程序,即如何将控制从一个进程切换到其他; 以及获得正确数据结构的困难,因为 C 的原始版本没有结构。

“这些事情的结合导致肯在整个夏天都放弃了,”里奇说。“在这一年中,我添加了结构,并且可能使编译器代码更好——更好的代码——所以在接下来的夏天,那是我们齐心协力并实际上用 C 语言重做整个操作系统的时候。”


这是我的意思的一个完美的例子。从评论:

指针的存在只是为了让编写编译器更容易?不,指针的存在是因为它们是对间接概念的最简单的抽象。——亚当·罗森菲尔德(一小时前)

你说的对。为了实现间接,指针是实现的最简单的抽象。它们绝不是最容易理解或使用的。数组要容易得多。

问题?要像指针一样高效地实现数组,您几乎必须在编译器中添加大量代码。

他们没有理由不能在没有指针的情况下设计 C,而是使用如下代码:

int i=0;
while(src[++i])
    dest[i]=src[i];

需要付出很多努力(在编译器部分)才能排除显式 i+src 和 i+dest 添加并使其创建与以下代码相同的代码:

while(*(dest++) = *(src++))
    ;

事后分解出变量“i”是困难的。新的编译器可以做到这一点,但在当时这是不可能的,而且运行在糟糕硬件上的操作系统几乎不需要像这样的优化。

现在很少有系统需要这种优化(我在最慢的平台之一上工作——有线电视机顶盒,我们的大部分东西都是用 Java 编写的),在极少数情况下你可能需要它,新的 C 编译器应该足够聪明,可以自己进行这种转换。

于 2009-04-15T17:43:30.330 回答
17

The Design and Evolution of C++中,Stroustrup 又给出了一个理由……

同一个头文件可以有两个或多个实现文件,可以由多个程序员同时处理,而不需要源代码控制系统。

这些天来这可能看起来很奇怪,但我想这是 C++ 发明时的一个重要问题。

于 2009-04-16T04:35:36.907 回答
14

如果你想要没有头文件的 C++,那么我有个好消息要告诉你。

它已经存在并被称为 D ( http://www.digitalmars.com/d/index.html )

从技术上讲,D 似乎比 C++ 好很多,但目前它还不够主流,无法在许多应用程序中使用。

于 2009-04-15T19:06:26.450 回答
8

C++ 的目标之一是成为 C 的超集,如果它不支持头文件,就很难做到这一点。而且,通过扩展,如果您希望删除头文件,您不妨考虑完全删除 CPP(预处理器,而不是 plus-plus);C# 和 Java 都没有在它们的标准中指定宏预处理器(但应该注意,在某些情况下,它们甚至可以与这些语言一起使用)。

由于现在设计了 C++,因此您需要原型(就像在 C 中一样)来静态检查任何引用外部函数和类的编译代码。如果没有头文件,您必须在使用它们之前输入这些类定义和函数声明。为了让 C++ 不使用头文件,您必须在语言中添加一个支持 Javaimport关键字之类的功能。那将是一个重大的补充和改变;回答你是否可行的问题:我不这么认为——一点也不。

于 2009-04-15T17:49:23.390 回答
8

很多人都意识到头文件的缺点,并且有想法将更强大的模块系统引入 C++。您可能想看看Daveed Vandevoorde 的Modules in C++ (Revision 5)

于 2009-07-13T17:49:11.927 回答
2

好吧,由于向后兼容性,C++ 本身不应该消除头文件。但是,我确实认为它们总体上是一个愚蠢的想法。如果要分发闭源库,可以自动提取此信息。如果您想了解如何在不查看实现的情况下使用类,这就是文档生成器的用途,它们的工作要好得多。

于 2009-04-15T20:15:16.283 回答
2

在实现文件的单独组件中定义类接口是有价值的。

它可以用接口来完成,但如果你走这条路,那么你就隐含地说类在将实现与契约分离方面存在缺陷。

Modula 2 有正确的想法,定义模块和实现模块。 http://www.modula2.org/reference/modules.php

Java/C# 的答案是相同的隐式实现(尽管是面向对象的。)

头文件很杂乱,因为头文件表达了实现细节(例如私有变量)。

在转向 Java 和 C# 时,我发现如果一种语言需要 IDE 支持来开发(这样公共类接口可以在类浏览器中导航),那么这可能是一种声明,即代码本身并不能作为特别可读。

我发现接口与实现细节的混合非常可怕。

至关重要的是,缺乏将公共类签名记录在独立于实现的简明注释文件中的能力,这表明语言设计是为了方便作者而编写的,而不是为了方便维护。好吧,我现在正在谈论 Java 和 C#。

于 2009-07-13T17:03:50.440 回答
2

这种分离的一个优点是很容易只查看界面,而不需要高级编辑器

于 2009-09-11T10:18:59.200 回答
1

如果你想知道这永远不会发生的原因:它会破坏几乎所有现有的 C++ 软件。如果您查看 C++ 委员会的一些设计文档,他们会查看各种替代方案,看看它会破坏多少代码。

将 switch 语句更改为智能的东西会容易得多。那只会破坏一点代码。它仍然不会发生。

为新想法编辑:

C++ 和 Java 之间使得 C++ 头文件成为必要的区别在于 C++ 对象不一定是指针。在 Java 中,所有类实例都由指针引用,尽管看起来并非如此。C++ 在堆和栈上分配了对象。这意味着 C++ 需要一种方法来了解对象的大小以及数据成员在内存中的位置。

于 2009-04-15T19:25:25.500 回答
1

没有头文件就没有语言。这是一个神话。

查看 Java 的任何专有库分发(我没有 C# 经验可言,但我希望它是相同的)。他们没有给你完整的源文件;他们只是给你一个文件,其中每个方法的实现都是空白的({}{return null;}类似的),以及他们可以隐藏的所有东西。除了标题之外,你不能称之为任何东西。

然而,没有技术原因,为什么 C 或 C++ 编译器可以计算一个适当标记的文件中的所有内容,extern除非该文件是直接编译的。然而,编译的成本将是巨大的,因为 C 和 C++ 解析速度都不是很快,这是一个非常重要的考虑因素。任何更复杂的合并标头和源代码的方法都会很快遇到技术问题,例如编译器需要知道对象的布局。

于 2009-04-16T02:48:20.847 回答
0

头文件是语言的一个组成部分。没有头文件,所有的静态库、动态库,几乎任何预编译的库都变得毫无用处。头文件还可以更轻松地记录所有内容,并且可以查看库/文件的 API,而无需查看每一位代码。

它们还使组织程序变得更容易。是的,您必须不断地从源代码切换到标头,但它们还允许您在实现中定义内部和私有 API。例如:

我的来源.h:

extern int my_library_entry_point(int api_to_use, ...);

我的来源.c:

int private_function_that_CANNOT_be_public();

int my_library_entry_point(int api_to_use, ...){
  // [...] Do stuff
}

int private_function_that_CANNOT_be_public() {

}

如果你#include <MySource.h>,那么你得到my_library_entry_point

如果你#include <MySource.c>,那么你得到private_function_that_CANNOT_be_public

如果你有一个函数来获取密码列表,或者一个实现你的加密算法的函数,或者一个会暴露操作系统内部的函数,或者一个覆盖权限的函数,你就会明白这可能是一件非常糟糕的事情,等等

于 2015-08-23T19:49:22.883 回答
-1

哦是的!

在 Java 和 C# 编码之后,每个类都有 2 个文件真的很烦人。所以我在想如何在不破坏现有代码的情况下合并它们。

事实上,这真的很容易。只需将定义(实现)放在#ifdef 部分中,然后在编译器命令行上添加定义来编译该文件。就是这样。

这是一个例子:

/* File ClassA.cpp */

#ifndef _ClassA_
#define _ClassA_

#include "ClassB.cpp"
#include "InterfaceC.cpp"

class ClassA : public InterfaceC
{
public:
    ClassA(void);
    virtual ~ClassA(void);

    virtual void methodC();

private:
    ClassB b;
};

#endif

#ifdef compiling_ClassA

ClassA::ClassA(void)
{
}

ClassA::~ClassA(void)
{
}

void ClassA::methodC()
{
}

#endif

在命令行上,编译该文件

-D compiling_ClassA

其他需要包含 ClassA 的文件就可以了

#include "ClassA.cpp"

当然,在命令行上添加定义可以很容易地通过宏扩展(Visual Studio 编译器)或自动变量(gnu make)添加,并对定义名称使用相同的命名法。

于 2009-07-13T16:13:30.607 回答
-3

我仍然不明白一些陈述的意义。API和实现分离是一件非常好的事情,但是头文件不是API。那里有私人领域。如果您添加或删除私有字段,您会更改实现而不是 API。

于 2010-07-22T16:38:35.193 回答