在 C++ 中分离声明和定义有哪些优点和缺点?

发布于 2024-07-14 23:58:28 字数 230 浏览 6 评论 0原文

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

function someFunc();

function someFunc()
{
  //Implementation.
}

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

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

In C++, declaration and definition of functions, variables and constants can be separated like so:

function someFunc();

function someFunc()
{
  //Implementation.
}

In fact, in the definition of classes, this is often the case. A class is usually declared with it's members in a .h file, and these are then defined in a corresponding .C file.

What are the advantages & disadvantages of this approach?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

悲凉≈ 2024-07-21 23:58:29

它是 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 语句 - 但这就是包含防护的用途。

优点:

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

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

It's an artefact of how C/C++ compilers work.

As a source file gets compiled, the preprocessor substitutes each #include-statement with the contents of the included file. Only afterwards does the compiler try to interpret the result of this concatenation.

The compiler then goes over that result from beginning to end, trying to validate each statement. If a line of code invokes a function that hasn't been defined previously, it'll give up.

There's a problem with that, though, when it comes to mutually recursive function calls:

void foo()
{
  bar();
}

void bar()
{
  foo();
}

Here, foo won't compile as bar is unknown. If you switch the two functions around, bar won't compile as foo is unknown.

If you separate declaration and definition, though, you can order the functions as you wish:

void foo();
void bar();

void foo()
{
  bar();
}

void bar()
{
  foo();
}

Here, when the compiler processes foo it already knows the signature of a function called bar, and is happy.

Of course compilers could work in a different way, but that's how they work in C, C++ and to some degree Objective-C.

Disadvantages:

None directly. If you're using C/C++ anyway, it's the best way to do things. If you've got a choice of language/compiler, then maybe you can pick one where this is not an issue. The only thing to consider with splitting declarations into header files is to avoid mutually recursive #include-statements - but that's what include guards are for.

Advantages:

  • Compilation speed: As all included files are concatenated and then parsed, reducing the amount and complexity of code in included files will improve compilation time.
  • Avoid code duplication/inlining: If you fully define a function in a header file, each object file that includes this header and references this function will contain it's own version of that function. As a side-note, if you want inlining, you need to put the full definition into the header file (on most compilers).
  • Encapsulation/clarity: A well defined class/set of functions plus some documentation should be enough for other developers to use your code. There is (ideally) no need for them to understand how the code works - so why require them to sift through it? (The counter-argument that it's may be useful for them to access the implementation when required still stands, of course).

And of course, if you're not interested in exposing a function at all, you can usually still choose to define it fully in the implementation file rather than the header.

稳稳的幸福 2024-07-21 23:58:29

该标准要求使用函数时,声明必须在作用域内。 这意味着编译器应该能够根据原型(头文件中的声明)验证您传递给它的内容。 当然,对于可变参数的函数除外 - 此类函数不会验证参数。

当不需要时,请考虑 C。 当时,编译器没有将返回类型规范视为默认为 int。 现在,假设您有一个函数 foo(),它返回一个指向 void 的指针。 但是,由于您没有声明,编译器会认为它必须返回一个整数。 例如,在某些摩托罗拉系统上,整数和指针将在不同的寄存器中返回。 现在,编译器将不再使用正确的寄存器,而是将指针转换为另一个寄存器中的整数。 当你尝试使用这个指针时——一切都崩溃了。

在标头中声明函数就可以了。 但请记住,如果您在标头中声明和定义,请确保它们是内联的。 实现此目的的一种方法是将定义放在类定义中。 否则,请在前面添加 inline 关键字。 否则,当标头包含在多个实现文件中时,您将遇到 ODR 违规。

The standard requires that when using a function, a declaration must be in scope. This means, that the compiler should be able to verify against a prototype (the declaration in a header file) what you are passing to it. Except of course, for functions that are variadic - such functions do not validate arguments.

Think of C, when this was not required. At that time, compilers treated no return type specification to be defaulted to int. Now, assume you had a function foo() which returned a pointer to void. However, since you did not have a declaration, the compiler will think that it has to return an integer. On some Motorola systems for example, integeres and pointers would be be returned in different registers. Now, the compiler will no longer use the correct register and instead return your pointer cast to an integer in the other register. The moment you try to work with this pointer -- all hell breaks loose.

Declaring functions within the header is fine. But remember if you declare and define in the header make sure they are inline. One way to achieve this is to put the definition inside the class definition. Otherwise prepend the inline keyword. You will run into ODR violation otherwise when the header is included in multiple implementation files.

2024-07-21 23:58:29

将声明和定义分离到 C++ 头文件和源文件中有两个主要优点。 首先,当您的类/函数/无论是#include出现在不止一处。 其次,通过这种方式,可以将接口和实现分开。 您的类或库的用户只需查看您的头文件即可编写使用它的代码。 您还可以使用 Pimpl Idiom 更进一步,让用户每次库实现发生变化时,代码不必重新编译。

您已经提到了 .h 和 .cpp 文件之间代码重复的缺点。 也许我写 C++ 代码的时间太长了,但我不认为它有那么糟糕。 无论如何,每次更改函数签名时都必须更改所有用户代码,那么还需要一个文件吗? 当您第一次编写类并且必须从标头复制并粘贴到新的源文件时,这只会很烦人。

实践中的另一个缺点是,为了编写(和调试!)使用第三方库的良好代码,您通常必须查看其内部。 这意味着即使您无法更改源代码,也可以访问它。 如果您拥有的只是一个头文件和一个编译的目标文件,那么很难确定该错误是您的错还是他们的错。 此外,查看源代码可以让您深入了解如何正确使用和扩展文档可能未涵盖的库。 并非每个人都随其库附带 MSDN。 伟大的软件工程师有一个令人讨厌的习惯,就是用你的代码做你从未梦想过的事情。 ;-)

There are two main advantages to separating declaration and definition into C++ header and source files. The first is that you avoid problems with the One Definition Rule when your class/functions/whatever are #included in more than one place. Secondly, by doing things this way, you separate interface and implementation. Users of your class or library need only to see your header file in order to write code that uses it. You can also take this one step farther with the Pimpl Idiom and make it so that user code doesn't have to recompile every time the library implementation changes.

You've already mentioned the disadvantage of code repetition between the .h and .cpp files. Maybe I've written C++ code for too long, but I don't think it's that bad. You have to change all user code every time you change a function signature anyway, so what's one more file? It's only annoying when you're first writing a class and you have to copy-and-paste from the header to the new source file.

The other disadvantage in practice is that in order to write (and debug!) good code that uses a third-party library, you usually have to see inside it. That means access to the source code even if you can't change it. If all you have is a header file and a compiled object file, it can be very difficult to decide if the bug is your fault or theirs. Also, looking at the source gives you insight into how to properly use and extend a library that the documentation might not cover. Not everyone ships an MSDN with their library. And great software engineers have a nasty habit of doing things with your code that you never dreamed possible. ;-)

简单气质女生网名 2024-07-21 23:58:29

优点

只需包含声明即可从其他文件引用类。 然后可以在编译过程中链接定义。

Advantage

Classes can be referenced from other files by just including the declaration. Definitions can then be linked later on in the compilation process.

给不了的爱 2024-07-21 23:58:29

对于类/函数/任何内容,您基本上有两个视图:

声明,您在其中声明名称、参数和成员(在结构/类的情况下),以及定义,您在其中定义函数的功能。

缺点之一是重复,但一大优点是您可以将函数声明为 int foo(float f) 并将详细信息保留在实现(=定义)中,因此任何想要使用您的函数的人都可以使用它。函数 foo 仅包含您的头文件和指向库/对象文件的链接,因此库用户和编译器只需要关心定义的接口,这有助于理解接口并加快编译时间。

You basically have 2 views on the class/function/whatever:

The declaration, where you declare the name, the parameters and the members (in the case of a struct/class), and the definition where you define what the functions does.

Amongst the disadvantages are repetition, yet one big advantage is that you can declare your function as int foo(float f) and leave the details in the implementation(=definition), so anyone who wants to use your function foo just includes your header file and links to your library/objectfile, so library users as well as compilers just have to care for the defined interface, which helps understanding the interfaces and speeds up compile times.

野稚 2024-07-21 23:58:29

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

任何非开源(即专有)的库或第 3 方代码都不会随发行版一起实现。 大多数公司都不愿意放弃源代码。 最简单的解决方案是,只需分发允许使用 DLL 的类声明和函数签名即可。

免责声明:我并不是说它是对、错还是合理,我只是说我见过很多次。

One advantage that I haven't seen yet: API

Any library or 3rd party code that is NOT open source (i.e. proprietary) will not have their implementation along with the distribution. Most companies are just plain not comfortable with giving away source code. The easy solution, just distribute the class declarations and function signatures that allow use of the DLL.

Disclaimer: I'm not saying whether it's right, wrong, or justified, I'm just saying I've seen it a lot.

千鲤 2024-07-21 23:58:29

前向声明的一大优点是,如果仔细使用,您可以减少模块之间的编译时依赖性。

如果 ClassA.h 需要引用 ClassB.h 中的数据元素,通常可以在 ClassA.h 中仅使用前向引用,并将 ClassB.h 包含在 ClassA.cc 而不是 ClassA.h 中,从而减少编译时间依赖性。

对于大型系统来说,这可以节省大量的构建时间。

One big advantage of forward declarations is that when used carefully you can cut down the compile time dependencies between modules.

If ClassA.h needs to refer to a data element in ClassB.h, you can often use just a forward references in ClassA.h and include ClassB.h in ClassA.cc rather than in ClassA.h, thus cutting down a compile time dependency.

For big systems this can be a huge time saver on a build.

两人的回忆 2024-07-21 23:58:29

缺点

这会导致大量重复。 大多数函数签名需要放在两个或多个(如 Paulious 指出的)位置。

Disadvantage

This leads to a lot of repetition. Most of the function signature needs to be put in two or more (as Paulious noted) places.

無心 2024-07-21 23:58:29
  1. 分离提供了程序元素的干净、整洁的视图。
  2. 可以在不公开源的情况下创建和链接到二进制模块/库。
  3. 链接二进制文件而无需重新编译源代码。
  1. Separation gives clean, uncluttered view of program elements.
  2. Possibility to create and link to binary modules/libraries without disclosing sources.
  3. Link binaries without recompiling sources.
热风软妹 2024-07-21 23:58:29

如果正确完成,这种分离可以在仅更改实现的情况下减少编译时间。

When done correctly, this separation reduces compile times when only the implementation has changed.

一张白纸 2024-07-21 23:58:28

从历史上看,这是为了帮助编译器。 在使用名称之前,您必须为其提供名称列表 - 无论这是实际用法,还是前向声明(C 的默认函数原型除外)。

现代语言的现代编译器表明这不再是必需的,因此 C & C++(以及 Objective-C,可能还有其他)的语法在这里是历史包袱。 事实上,这是 C++ 的一大问题,即使添加适当的模块系统也无法解决。

缺点是:大量嵌套的包含文件(我之前跟踪过包含树,它们非常大)以及声明和定义之间的冗余 - 所有这些都会导致更长的编码时间和更长的编译时间(曾经比较过类似的 C++ 和C#项目?这是差异的原因之一)。 必须为您提供的任何组件的用户提供头文件。 ODR 违规的可能性。 对预处理器的依赖(许多现代语言不需要预处理器步骤),这使得您的代码更加脆弱并且更难以工具解析。

优点:不多。 您可能会争辩说,出于文档目的,您得到了一组聚集在一个位置的函数名称列表 - 但现在大多数 IDE 都具有某种代码折叠功能,并且任何规模的项目都应该使用文档生成器(例如 doxygen)。 有了更干净、无预处理器、基于模块的语法,工具就更容易遵循你的代码并提供这个和更多功能,所以我认为这个“优势”毫无意义。

Historically this was to help the compiler. You had to give it the list of names before it used them - whether this was the actual usage, or a forward declaration (C's default funcion prototype aside).

Modern compilers for modern languages show that this is no longer a necessity, so C & C++'s (as well as Objective-C, and probably others) syntax here is histotical baggage. In fact one this is one of the big problems with C++ that even the addition of a proper module system will not solve.

Disadvantages are: lots of heavily nested include files (I've traced include trees before, they are surprisingly huge) and redundancy between declaration and definition - all leading to longer coding times and longer compile times (ever compared the compile times between comparable C++ and C# projects? This is one of the reasons for the difference). Header files must be provided for users of any components you provide. Chances of ODR violations. Reliance on the pre-processor (many modern languages do not need a pre-processor step), which makes your code more fragile and harder for tools to parse.

Advantages: no much. You could argue that you get a list of function names grouped together in one place for documentation purposes - but most IDEs have some sort of code folding ability these days, and projects of any size should be using doc generators (such as doxygen) anyway. With a cleaner, pre-processor-less, module based syntax it is easier for tools to follow your code and provide this and more, so I think this "advantage" is just about moot.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文