C ++应该消除头文件吗?

时间:2009-04-15 17:20:21

标签: c++ header-files

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

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

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

17 个答案:

答案 0 :(得分:75)

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

答案 1 :(得分:33)

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

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

答案 2 :(得分:31)

即使Bjarne Stroustrup已将头文件称为kludge。

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

答案 3 :(得分:21)

我经常在C#和C ++之间翻转,而C#中缺少头文件是我最大的烦恼之一。我可以查看一个头文件,并学习我需要了解的关于一个类的内容 - 调用它的成员函数,它们的调用语法等 - 而不必涉及实现该类的代码页面。

是的,我知道部分类和#regions,但它不一样。部分类实际上会使问题变得更糟,因为类定义分布在多个文件中。就#regions而言,它们似乎永远不会像我现在所做的那样以我喜欢的方式进行扩展,因此我必须花时间扩展这些小的加号直到我得到正确的观点。

也许如果Visual Studio的intellisense更适合C ++,我不会有令人信服的理由不得不经常引用.h文件,但即使在VS2008中,C ++的intellisense也无法触及C#的

答案 4 :(得分:18)

The Design and Evolution of C++中,Stroustrup提出了另一个理由......

相同的头文件可以有两个或更多的实现文件,可以由多个程序员同时处理,而无需源控制系统。

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

答案 5 :(得分:15)

C使得编写编译器变得容易。它基于这一原则做了很多事情。仅存在指针使编写编译器更容易,头文件也是如此。转移到C ++的许多东西都基于与实现的这些功能的兼容性,以使编译器编写更容易。

实际上,这是一个好主意。当C被创建时,C和Unix就是一对。 C移植Unix,Unix运行C.这样,C和Unix可以迅速从平台传播到平台,而基于程序集的操作系统必须完全重写才能移植。

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

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

C是一种很棒的简单语言。它的功能集非常有限,您可以毫不费力地编写编译器。移植它通常是微不足道的!我并不是说它是一种糟糕的语言或任何东西,只是C的创建时的主要目标可能会留下现在或多或少不必要的语言中的残余,但是为了兼容性将保留它们。 p>


似乎有些人并不认为C是写入端口Unix的,所以这里:(from

  

编写了UNIX的第一个版本   用汇编语言,但汤普森的   意图是它会写   用高级语言。

     汤普森于1971年首次尝试使用   Fortran在PDP-7上,但放弃了   第一天过后。然后他写了一篇   他称之为B的非常简单的语言,   他参加了PDP-7。它   工作,但有问题。   首先,因为实施是   解释,它总是会   慢。二,B的基本概念,   这是基于单词导向的   BCPL,不适合   面向字节的机器就像新的一样   PDP-11。

     

Ritchie使用PDP-11添加类型   到B,有一段时间被称为NB   为“新B”,然后他开始   为它编写一个编译器。 “所以这样   C的第一阶段就是这两个阶段   第一,短期连续阶段   某些语言从B变化,真的,   添加类型结构也没有   语法上有很大的变化;并做   编译器,“里奇说。

     

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

     “事情的组合引起的   肯在夏天放弃了,“   里奇说。 “过去一年,我补充道   结构,并可能使   编译器代码稍好一点 -   更好的代码 - 等等   夏天,那是我们做的时候   齐心协力,实际上做了重做   C中的整个操作系统。“


这是我的意思的完美例子。来自评论:

  

仅存在指针使编写编译器更容易?没有。指针存在是因为它们是对间接思想最简单的抽象。 - Adam Rosenfield(一小时前)

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

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

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

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

需要花费很多精力(在编译器部分)来分解显式的i + src和i + dest添加,并使它创建相同的代码:

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

在事实之后考虑变量“i”是很难的。新的编译器可以做到这一点,但当时它是不可能的,并且在那些糟糕的硬件上运行的操作系统需要很少的优化。

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

答案 6 :(得分:14)

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

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

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

答案 7 :(得分:8)

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

由于C ++目前正在设计,您需要原型 - 就像在C中一样 - 以静态方式检查引用外部函数和类的任何编译代码。如果没有头文件,则必须在使用它们之前键入这些类定义和函数声明。对于C ++不使用头文件,您必须使用支持Java import关键字之类的语言添加功能。那将是一个重大的补充和改变;回答你是否实际的问题:我不这么认为 - 根本不是。

答案 8 :(得分:8)

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

答案 9 :(得分:2)

好吧,由于向后兼容性,C ++本身不应该消除头文件。但是,我认为他们总的来说是个愚蠢的想法。如果要分发闭源lib,可以自动提取此信息。如果你想了解如何使用没有查看实现的类,那就是文档生成器的用途,并且它们可以更好地完成工作。

答案 10 :(得分:2)

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

答案 11 :(得分:2)

在与实现文件分开的组件中定义类接口是有价值的。

可以通过接口来完成,但是如果你沿着这条路走下去,那么你隐含地说类在实现与合同分离方面存在缺陷。

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

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

头文件是一个kludge,因为头文件表达了实现细节(例如私有变量。)

在转移到Java和C#时,我发现如果一种语言需要IDE支持开发(这样公共类接口可以在类浏览器中导航),那么这可能是一个声明代码不支持它的具有特别可读性的优点。

我发现界面与实现细节的混合非常可怕。

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

答案 12 :(得分:1)

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

将switch语句更改为半智能的东西要容易得多。这只会打破一些代码。它仍然不会发生。

为新想法编辑:

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

答案 13 :(得分:0)

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

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

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

答案 14 :(得分:0)

头文件是该语言不可或缺的一部分。没有头文件,所有静态库,动态库,几乎任何预编译库都变得无用。头文件还可以更容易地记录所有内容,并且可以查看库/文件的API,而无需遍历每一段代码。

它们还可以更轻松地组织您的程序。是的,您必须不断地从源切换到标头,但它们还允许您在实现中定义内部和私有API。例如:

MySource.h:

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

MySource.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

如果你有一个获取密码列表的函数,或者一个实现你的加密算法的函数,或者一个暴露操作系统内部的函数,或者一个函数,你会看到这可能是一件非常糟糕的事情。超越特权等。

答案 15 :(得分:-1)

哦,是的!

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

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

以下是一个例子:

/* 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"

当然,在命令行中添加define可以通过宏扩展(Visual Studio编译器)或自动变量(gnu make)轻松添加,并使用相同的命名法命名。

答案 16 :(得分:-3)

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