如何在没有循环的情况下将实现移动到多个头文件? [英] How can we move implementation to multiple header file without circularity?

查看:45
本文介绍了如何在没有循环的情况下将实现移动到多个头文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于单个.cpp文件和.h文件 - 我们可以将实现从.cpp移动到.h文件,但对于多个文件,我不能为我的情况做这个并且发生循环,



我的情况是这样的:

(包含警卫更加复杂和深刻的模板):

for single .cpp file- and .h file- we can move implementation from .cpp to .h file, but for multiple files, i can not do this for my case and occurred circularity,

my case is something like this:
(more complicated and deeply templated with inclusion guard ):

//a.h

    #include"stdio.h"
    #include"b.h"

    class A
    {
    public:
    void showfromA(const B& b);
    int index;
   };

//a.cpp

    #include"a.h"
    void A::showfromA(const B& b)
    {
    printf("b.index=%i",b.index);
    }

//b.h

    class B
    {
    public:
    void showfromB();
    int index;
    };

//b.cpp

    #include"a.h"
    void B::showfromB()
    {
    A a;
    a.index=1;
    printf("a.index=%i",a.index);
    }

//main.cpp

    #include"b.h"
    main()
    {
    B b;
    b.showfromB();
    }





发生循环因为啊包括bh但b.cpp包括啊,

当.h文件和.cpp文件分开时,代码没问题我们没有循环性,但是当我们尝试将实现从.cpp移动到.h文件时,我们遇到了A类和B类之间的循环并编译错误。请注意,我想将.cpp文件中的方法实现移动到.h文件中的类定义。



"circularity occurred because a.h include b.h but b.cpp include a.h,
when .h files and .cpp files are separated ,the code is OK" and we do not have circularity but when we try to move implementation from .cpp to .h file we encounter to circularity between class A and B and compile error. note that i want to move method implementation from .cpp file into " the class definition" in .h file.

推荐答案

就是不要这样做!应该在头文件中的唯一实现是模板 - 其他所有实现都在实现文件中。这有很多原因,这不仅是一个友好的建议,而是强制性的!除此之外,如果你违反了规则,编译器和/或链接器将无法理解你的代码:你已经看到了自己的错误信息。



考虑编译器和链接器执行的任务:



1.编译器调用预编译器,它将解释以开头的所有语句#。除此之外,预编译器将使用指定头文件的全部内容替换每个 #include 语句



2.预编译器完成后,编译器将逐行读取实现文件,并将其转换为汇编代码。要翻译任何代码段,编译器必须知道每个符号代表什么,以及如何定义代码中使用的每个变量和类型。因此,必须在使用它们的代码之前定义所有类型和变量。编译器还将跟踪生成的代码中每个函数的相对地址,同时为函数的每次调用生成标记。 (或者,链接器也可以做一些工作)



3.链接器将使用汇编代码将各个文件连接到一个大文件中。然后,它将为每个函数的起始地址创建查找表。这将使计算机在遇到代码中的函数调用时跳转到正确的位置。



现在考虑将所有实现复制到标题中时会发生什么file:

1.预编译器将用指定文件的内容替换所有 #include 语句。递归!从 a.cpp 开始,它找到 #includea.h,将其替换为内容。然后它继续扫描代码,从 a.h 的插入文本开始,找到 #includeb.h。忠实地,它将语句替换为 b.h 的全部内容,现在还包含实现。预编译器继续扫描代码,这次从新插入的 bh 开始,然后找到 #include啊



此时,预编译器意识到它被告知包含一个已经包含的文件,更糟糕的是,还没有被扫描到最后。这就产生了你看到的错误:预编译器由于你的轻率 #include 语句而遇到了无休止的递归(或者,更重要的是,因为你的愚蠢想法将实现移动到头文件中。



但是让我们保持乐观,让我们说你可以用修复代码#include 警卫解决无休止的递归问题,预编译器可以完成其工作。



1.预编译器成功完成



2.编译器从 a.cpp 生成生成的预编译文件。因为 #include s bh ,它会找到a和b的所有函数的实现,因此它为 A :: showfromA() B生成汇编代码:showfromB()。它还为类 A B 生成类型定义,但这些只在内部使用,以生成汇编代码正确。函数的汇编代码放入目标文件 a.obj 。然后编译器从 b.cpp 中获取预编译结果,并再次查找所有实现。因此,它将再次生成 A :: showfromA B :: showfromB()的汇编代码。这将生成 b.obj



3.然后链接器选择 a.obj b.obj ,将它们连接成一个文件,并开始为函数调用生成查找表:它找到 A :: showfromA() B :: showfromB() in a.obj ,并为它们生成条目。然后输入 b.obj ,找到 A :: showfromA()。它尝试生成一个条目,但发现已有一个条目!但哪个是正确的呢?它无法决定,因此条目含糊不清 - 当代码包含对 A :: showfromA()的函数调用时,应该调用哪个函数?链接器不知道,因此它发出错误!



如何解决这个问题?简单:不要将任何实现放入头文件中。



只有一个例外:模板函数。模板函数没有上述问题,因为它们实际上不是C / C ++代码 - 实际的C / C ++代码是由编译器本身生成的。
Just don't do it! The only implementations that should be in a header file are templates - everything else belongs in the implementation files. There are many reasons why this is not only a friendly advice, but mandatory! Among other things, the compiler and/or linker won't be able to understand your code if you break that rule: you've seen for yourself the error messages.

Consider the tasks that the compiler and linker perform:

1. The compiler invokes the precompiler, which will interpret all the statements starting with '#'. Among other things, the precompiler will replace every #include statement with the entire contents of the specified header file

2. When the precompiler is done, the compiler will read the implementation file line by line, and translate it into assembly code. To translate any piece of code, the compiler must know what each symbol stands for, and how each variable and type is defined that is used in the code. Therefore, all the types and variables must be defined before the code that uses them. The compiler will also keep track of the relative address of each function within the generated code, and at the same time generate a marker for every call of a function. (or, the linker could also do some of that work)

3. The linker will join the individual files with the assembly code into one big file. It will then create lookup tables for the starting address of each function. That will enable the computer to jump to the correct location when it encounters a function call in the code.

Now consider what happens when you copy all your implementations into the header file:
1. The precompiler will replace all #include statements with the contents of the specified file. Recursively! Starting at a.cpp, it finds #include "a.h", replaces it with the contents. Then it continues scanning the code, starting at the inserted text from a.h, and finds #include "b.h". Faithfully, it replaces the statement with the entire contents of b.h, which now also contain the implementation. The precompiler continues scanning the code, this time from the beginning of the newly inserted b.h, then finds #include "a.h"

At this point, the precompiler realizes that it is told to include a file that has already been included, and worse, hasn't yet been scanned to the end. This is what produces the error you see: the precompiler has run into an endless recursion because of your thoughtless #include statements (or, more to the point, because of your dumb idea to move implementations into header files).

But let's be optimistic, and let's say that you can fix the code with #include guards to resolve the endless recursion and the precompiler can finish its work.

1. The precompiler completes successfully

2. The compiler takes the resulting files precompiled files generated from a.cpp. Because a.h #includes b.h, it will find the implementations of all functions for a and b, so it generates assembly code for both A::showfromA() and B:showfromB(). It also generates type definitions for the classes A and B, but these are only used internally, to generate the assembly code correctly. The assembly code of the functions is put into the object file a.obj. Then the compiler takes the precompiled results from b.cpp, and again finds all implementations. It will therefore, again, generate assembly code for A::showfromA and B::showfromB(). This will be generated into b.obj.

3. Then the linker picks up a.obj and b.obj, joins them into a single file, and starts generating a lookup table for the function calls: it finds A::showfromA() and B::showfromB() in a.obj, and generates entries for them. Then it enters b.obj, and finds A::showfromA(). It tries generating an entry, but finds there already is one! But which is the correct one? It can't decide, so the entry is ambiguous - when the code contains a function call to A::showfromA(), which function should be called? The linker doesn't know, therefore it issues an error!

How to resolve this? Easy: do not put any implementation into your header files.

There is only one exception: template functions. Template functions don't have the issue described above, because they are not in fact C/C++ code - the actual C/C++ code is generated by the compiler itself.


为了避免这种混乱你可以使用前瞻性声明,这意味着



To avoid such mess you can use "forward declaration", it means

//a.h

    #include"stdio.h"

//forward b
class B;
 
    class A
    {
    public:
    void showfromA(const B& b);
    int index;
   };

//a.cpp

    #include"a.h"
//here it comes
 #include"b.h"
    void A::showfromA(const B& b)
    {
    printf("b.index=%i",b.index);
    }





考虑重新设计课程。这样的循环问题是设计不良的症状,有一天会让你感到困扰。



Consider to redesign your classes. Such circular problems are a symptom of poor design which will hurd you some day.


这篇关于如何在没有循环的情况下将实现移动到多个头文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆