我可以编写没有标题的C ++代码(重复函数声明)吗?

有没有办法不必编写函数声明两次(头文件),仍然保持编译相同的可扩展性,debugging的清晰度和在C ++编程时的devise灵活性?

使用lzz 。 它只需要一个文件,并在正确的位置为所有的声明/定义自动创build.h和.cpp文件。

lzzfunction非常强大,可以处理完整的C ++语法的99%,包括模板,专业化等等。

更新150120:

较新的C ++ '11 / 14语法只能在lzz函数体中使用。

当我开始写C时,我也有同样的感受,所以我也研究过这个。 答案是,是的,这是可能的,不,你不想。

首先是的。

在GCC中,你可以这样做:

// foo.cph void foo(); #if __INCLUDE_LEVEL__ == 0 void foo() { printf("Hello World!\n"); } #endif 

这具有预期的效果:将头文件和源文件合并到一个文件中,这两个文件都可以被包含和链接。

然后用no:

这只适用于编译器可以访问整个源代码的情况。 编写一个你想分发的库但是保持闭源的时候,你不能使用这个技巧。 要么分发完整的.cph文件,要么必须编写一个单独的.h文件来处理.lib文件。 虽然也许你可以用macros预处理器自动生成它。 它会变得毛茸茸的。

而原因2为什么你不想要这个,这可能是最好的: 编译速度 。 通常情况下,C源文件只需在文件本身发生更改时重新编译,或者包含更改的文件。

  • C文件可以经常更改,但更改只涉及重新编译已更改的一个文件。
  • 头文件定义接口,所以他们不应该经常改变。 然而,当他们做,他们触发重新编译每个包含它们的源文件

当所有的文件都被合并为头文件和源文件时,每一个改变都会触发所有源文件的重新编译。 C ++现在还不知道它的快速编译时间,想象一下当整个项目每次都需要重新编译时会发生什么。 然后外推到一个具有复杂的依赖关系的数百个源文件的项目…

对不起,在C ++中不存在用于消除头文件的“最佳实践”:这是一个坏主意,句号。 如果你非常讨厌他们,你有三个select:

  • 熟悉C ++内部函数和你正在使用的编译器; 你会碰到不同于一般的C ++开发者的问题,你可能需要在没有很多帮助的情况下解决它们。
  • select一种可以使用“正确”的语言而不会感到沮丧
  • 获取一个工具为你生成它们; 你仍然有标题,但你节省了一些打字的努力

没有可行的方法来解决标题。 你唯一能做的就是把所有的代码放到一个大的c ++文件中。 那最终会变成一个难以维系的混乱,所以请不要这样做。

目前C ++的头文件是一个恶习。 我不喜欢他们,但他们没有办法。 尽pipe如此,我还是希望看到一些改进和新的想法。

顺便说一句 – 一旦你已经习惯了它已经不是那么糟糕了。C + +(和任何其他语言)有更多的烦人的事情。

在他的文章“ 简单支持C ++devise合同”中 ,Pedro Guerreiro说:

通常,C ++类有两个文件:头文件和定义文件。 我们应该在哪里写断言:在头文件中,因为断言是规范? 或者在定义文件中,因为它们是可执行的? 或者两者都存在不一致的风险(和重复工作)? 相反,我们推荐我们放弃传统的风格,并且只使用头文件去掉定义文件,好像所有的函数都是内联定义的,就像Java和Eiffel一样。

与C ++的正常性相比,这是一个巨大的变化,它有可能在一开始就冒着失败的风险。 另一方面,为每个类保留两个文件是非常尴尬的,迟早会出现一个C ++开发环境,从而隐藏了我们,使我们能够专注于我们的类,而不必担心它们的存储位置。

那是2001年,我同意了。 现在是2009年,现在仍然没有“发展的环境会出现,从我们这里隐藏,让我们专注于我们的class”已经出来。 相反,冗长的编译时间是常态。

幸运的是,我们现在有了C#,在这里我们可以无需使用头文件…而且编译时间长:)


注意:上面的链接现在似乎已经死了。 这是对出版物的完整引用,正如它出现在作者网站的出版物部分:

Pedro Guerreiro,简单支持C ++devise合同,TOOLS USA 2001,Proceedings,24-34,IEEE,2001。

我所看到的像你这样的一些人是把所有的东西写在标题中 。 这给你只需要写一次方法configuration文件所需的属性。

我个人认为,为什么把声明和定义分开是有好处的,但如果这让你感到困扰,那么你就有办法做到你想要的。

你必须写两次函数声明 ,实际上(一次在头文件中,一次在实现文件中)。 函数的定义(AKA实现)将在实现文件中写入一次。

你可以把所有的代码写在头文件中(这实际上是C ++通用编程中非常常用的做法),但是这意味着每个包含头文件的C / CPP文件都意味着从这些头文件中重新编译实现。

如果您正在考虑类似于C#或Java的系统,那么在C ++中是不可能的。

有头文件生成软件。 我从来没有使用它,但它可能是值得研究。 例如,检查出mkhdr ! 它应该扫描C和C ++文件并生成相应的头文件。

(但是,正如Richard指出的那样,这似乎限制了你使用某些C ++的function。

其实…你可以把整个实现写在一个文件中。 模板化的类都在头文件中定义,没有cpp文件。

你也可以用你想要的任何扩展名保存。 然后在#include语句中,您将包含您的文件。

 /* mycode.cpp */ #pragma once #include <iostreams.h> class myclass { public: myclass(); dothing(); }; myclass::myclass() { } myclass::dothing() { // code } 

然后在另一个文件中

 /* myothercode.cpp */ #pragma once #include "mycode.cpp" int main() { myclass A; A.dothing(); return 0; } 

您可能需要设置一些构build规则,但它应该工作。

你可以避免标题。 完全。 但我不推荐它。

你将面临一些非常具体的限制。 其中之一是你将无法获得循环引用(你将不能有父类包含一个指向类ChildNode的实例的指针,而类ChildNode也包含一个指向类Parent实例的指针。必须是一个或另一个。)

还有其他限制,最终导致你的代码真的很奇怪。 坚持标题。 你会学会如何喜欢它们(因为它们提供了一个类可以做的很好的概要)。

为了提供rix0rrr的stream行答案的变体:

 // foo.cph #define INCLUDEMODE #include "foo.cph" #include "other.cph" #undef INCLUDEMODE void foo() #if !defined(INCLUDEMODE) { printf("Hello World!\n"); } #else ; #endif void bar() #if !defined(INCLUDEMODE) { foo(); } #else ; #endif 

我不build议这样做,我认为这种结构表明了以死记硬背的代价去除内容的重复。 我想这使复制面食更容易? 这不是一个美德。

就像所有其他这种性质的技巧一样,对函数主体的修改仍然需要重新编译所有文件,包括包含该函数的文件。 非常小心的自动化工具可以部分避免这种情况,但是他们仍然必须parsing源文件来检查,并且仔细地构build,以便在没有不同的情况下不重写输出。

对于其他读者:我花了几分钟的时间试图找出这种格式的守卫,但没有拿出任何好的东西。 注释?

在阅读所有其他答案之后,我发现它缺less了正在进行的工作来添加对C ++标准中的模块的支持。 它不会达到C ++ 0x,但意图是,它将在稍后的技术审查(而不是等待一个新的标准,这将需要年龄)解决。

正在讨论的提案是N2073 。

它的不好的部分是,你不会得到,甚至没有最新的C ++ 0x编译器。 你将不得不等待。 与此同时,您将不得不在唯一标题库中的定义的唯一性和编译成本之间做出妥协。

Visual Studio 2012下还没有人提到Visual-Assist X。

它有一堆菜单和热键,可以用来缓解维护标题的麻烦:

  • “创build声明”将函数声明从当前函数复制到.hpp文件中。
  • “Refactor..Change签名”允许您用一个命令同时更新.cpp和.h文件。
  • Alt-O允许您立即翻转.cpp和.h文件。

据我所知,不。 头文件是C ++作为一种语言的固有部分。 不要忘记,forward声明允许编译器仅仅包含一个指向编译对象/函数的函数指针,而不必包含整个函数(你可以通过声明一个函数内联(如果编译器感觉像这样)来获得。

如果真的,真的很讨厌制作头文件,那就写一个perl脚本来自动生成头文件。 我不确定我会推荐它。

你可以不用标题。 但是,为什么要花费精力避免仔细研究多年来由专家开发的最佳实践。

当我写基本的,我很喜欢行号。 但是,我不会想把它们塞进C ++,因为这不是C ++的方式。 这同样适用于标题…我相信其他答案解释所有的推理。

我明白你的问题。 我会说,C ++的主要问题是它从Cinheritance的编译/构build方法.C / C ++头结构是在编码涉及较less定义和更多实现的时候devise的。 不要把瓶子扔在我身上,但这就是它的样子。

自那时以来,面向对象已经征服了世界,世界更多的是关于定义和实现。 因此,包括头文件在内的基本集合(比如STL中的基本集合)使用编译器处理众所周知的难度较大的模板时会非常痛苦。 对于TDD,重构工具,一般的开发环境来说,所有这些带有预编译头文件的魔法都没有什么帮助。

当然,C程序员并没有受到太多的困扰,因为他们没有编译器繁重的头文件,所以他们对这个非常简单,低级别的编译工具链感到满意。 使用C ++,这是一个痛苦的历史:无止境的前向声明,预编译头文件,外部parsing器,自定义预处理器等。

然而,很多人并没有意识到C ++是唯一具有强大和现代解决scheme的高级和低级问题的语言。 很容易地说,你应该去适当的反思和build立系统的其他语言,但这是不必要的,我们必须牺牲低级的编程解决scheme,我们需要复杂的事情与低级语言混合与一些基于虚拟机/ JIT的解决scheme。

我有这个想法一段时间了,现在有一个基于“单元”的C ++工具链是最酷的东西,类似于在D中。问题出现在跨平台部分:对象文件能够存储任何信息,没有问题,但是由于在Windows上,目标文件的结构与ELF不同,所以实现跨平台的解决scheme来存储和处理中间文件编译单元。

没有头文件是完全可能的。 可以直接包含源文件:

 #include "MyModule.c" 

与此相关的主要问题是循环依赖(即:在C中,您必须在调用它之前声明一个函数)。 如果您完全自上而下地devise您的代码,这不是一个问题,但是如果您不习惯这种devise模式,则可能需要一些时间才能将这些devise模式包装起来。

如果你绝对必须有循环依赖,可以考虑创build一个专门用于声明的文件,并在其他所有文件之前包含它。 这有点不方便,但是比每个C文件头都要less。

我目前正在使用这种方法开发我的一个主要项目。 以下是我所经历的优势细分:

  • 源树中的文件污染less得多。
  • 更快的构build时间。 (只有一个目标文件是由编译器生成的,main.o)
  • 更简单的制作文件。 (只有一个目标文件是由编译器生成的,main.o)
  • 没有必要“干净”。 每个构build都是“干净的”。
  • 较less的锅炉代码。 较less的代码=较less的潜在错误。

我发现Gish(Cryptic Sea的一款游戏Edmund McMillen)在其自己的源代码中使用了这种技术的变种。

你可以仔细地devise你的函数,这样所有的依赖函数都会在它们的依赖关系之后被编译,但是正如Nils暗示的那样,这是不实际的。

Catalin(原谅失踪的变音符号)也build议在头文件中定义你的方法的更实用的select。 这实际上可以在大多数情况下工作..特别是如果你有在你的头文件守卫,以确保他们只包括一次。

我个人认为,头文件+声明函数更适合'让你的头'新代码,但这是我个人的偏好,我想…

实际目的不是,这是不可能的。 技术上,是的,你可以。 但坦率地说,这是对语言的滥用,你应该适应这种语言。 或移动到像C#的东西。

使用头文件是最好的做法,一段时间后,它会成长为你。 我同意只有一个文件比较容易,但也可能导致错误的编码。

这些东西中的一些,虽然感到尴尬,但能让你获得更多的满足感。

作为例子考虑指针,通过值/参考传递参数等。

对于我来说,头文件允许我保持我的项目结构合理

学会认识到头文件是一件好事。 它们将代码如何显示给另一个用户,从而实现其实际执行操作的方式。

当我使用某人的代码的时候,我现在不得不通过所有的实现来看看这个类的方法是什么。 我关心的是代码的function,而不是如何去做。

这已经“复活”,由于重复…

在任何情况下,标题的概念都是值得的,即将接口从实现细节中分离出来。 标题概述了你如何使用类/方法,而不是如何使用它。

缺点是标题内的细节和所有必要的解决方法。 这些是我看到的主要问题:

  • 依赖生成。 当标题被修改时,包含这个标题的任何源文件都需要重新编译。 这个问题当然是确定哪些源文件实际使用它。 当执行“干净的”构build时,通常需要将信息caching在某种依赖关系树中以备后用。

  • 包括警卫。 好吧,我们都知道如何写这些,但在一个完美的系统中,这是不必要的。

  • 私人细节。 在一个课堂上,你必须把私人的细节放在标题中。 是的,编译器需要知道类的“大小”,但是在一个完美的系统中,它将能够在以后的阶段中绑定它。 这会导致像pImpl这样的各种解决方法,并且即使仅仅因为要隐藏依赖项而只有一个实现,也可以使用抽象基类。

完美的系统将与之合作

  • 单独的类定义和声明
  • 这两者之间有一个明确的约束,因此编译器会知道类声明及其定义在哪里,并且知道类的大小。
  • 你声明using class而不是预处理器#include 。 编译器知道在哪里find一个类。 一旦你完成了“使用类”,你可以使用该类的名字,而不限制它。

我很想知道D是如何做到的。

关于是否可以使用C ++而不使用头文件,我会说不需要抽象基类和标准库。 除了你可以得到没有他们,虽然你可能不想。

你也可以只使用一个 header file ,而不用c++ file

简单地说,你添加header file而不是创build引用它的c++ file 。 所以,你直接在header添加代码,因此可以用它作为code file

这是一个 没有使用 c++ file header file 的例子

 #pragma once #include <windows.h> static class ConsoleUtils { public: static BOOL SetConsoleDisplayMode(HANDLE hOutputHandle, DWORD dwNewMode) { typedef BOOL(WINAPI *SCDMProc_t) (HANDLE, DWORD, LPDWORD); SCDMProc_t SetConsoleDisplayMode; HMODULE hKernel32; BOOL bFreeLib = FALSE, ret; const char KERNEL32_NAME[] = "kernel32.dll"; hKernel32 = GetModuleHandleA(KERNEL32_NAME); if (hKernel32 == NULL) { hKernel32 = LoadLibraryA(KERNEL32_NAME); if (hKernel32 == NULL) return FALSE; bFreeLib = true; } SetConsoleDisplayMode = (SCDMProc_t)GetProcAddress(hKernel32, "SetConsoleDisplayMode"); if (SetConsoleDisplayMode == NULL) { SetLastError(ERROR_CALL_NOT_IMPLEMENTED); ret = FALSE; } else { DWORD tmp; ret = SetConsoleDisplayMode(hOutputHandle, dwNewMode, &tmp); } if (bFreeLib) FreeLibrary(hKernel32); return ret; } };