由于类之间的循环依赖关系,解决构建错误。

时间:2020-12-02 09:44:08

I often find myself in a situation where I am facing multiple compilation/linker errors in a C++ project due to some bad design decisions (made by someone else :) ) which lead to circular dependencies between C++ classes in different header files (can happen also in the same file). But fortunately(?) this doesn't happen often enough for me to remember the solution to this problem for the next time it happens again.

我经常发现,在c++项目中,由于一些糟糕的设计决策(由其他人做出的),我面临着多个编译/链接错误,这导致了不同头文件中c++类之间的循环依赖(也可能发生在同一个文件中)。但幸运的是(?)这种情况并不经常发生,我不记得下次再发生这个问题时的解决方法。

So for the purposes of easy recall in the future I am going to post a representative problem and a solution along with it. Better solutions are of-course welcome.

为了便于以后回忆,我将提出一个有代表性的问题和一个解决方案。更好的解决方案当然是受欢迎的。


  • A.h

    A.h

    class B;
    class A
    {
        int _val;
        B *_b;
    public:
    
        A(int val)
            :_val(val)
        {
        }
    
        void SetB(B *b)
        {
            _b = b;
            _b->Print(); // COMPILER ERROR: C2027: use of undefined type 'B'
        }
    
        void Print()
        {
            cout<<"Type:A val="<<_val<<endl;
        }
    };
    

  • B.h

    B.h

    #include "A.h"
    class B
    {
        double _val;
        A* _a;
    public:
    
        B(double val)
            :_val(val)
        {
        }
    
        void SetA(A *a)
        {
            _a = a;
            _a->Print();
        }
    
        void Print()
        {
            cout<<"Type:B val="<<_val<<endl;
        }
    };
    

  • main.cpp

    main.cpp

    #include "B.h"
    #include <iostream>
    
    int main(int argc, char* argv[])
    {
        A a(10);
        B b(3.14);
        a.Print();
        a.SetB(&b);
        b.Print();
        b.SetA(&a);
        return 0;
    }
    

8 个解决方案

#1


215  

The way to think about this is to "think like a compiler".

思考这个问题的方法是“像编译器一样思考”。

Imagine you are writing a compiler. And you see code like this.

假设您正在编写一个编译器。你会看到这样的代码。

// file: A.h
class A {
  B _b;
};

// file: B.h
class B {
  A _a;
};

// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
  A a;
}

When you are compiling the .cc file (remember that the .cc and not the .h is the unit of compilation), you need to allocate space for object A. So, well, how much space then? Enough to store B! What's the size of B then? Enough to store A! Oops.

在编译.cc文件时(请记住,.cc而不是.h是编译单元),您需要为对象a分配空间,那么,那么,那么,有多少空间呢?足够的存储B !B的大小是多少?足够的存储!哦。

Clearly a circular reference that you must break.

很明显是一个循环的参考,你必须打破。

You can break it by allowing the compiler to instead reserve as much space as it knows about upfront - pointers and references, for example, will always be 32 or 64 bits (depending on the architecture) and so if you replaced (either one) by a pointer or reference, things would be great. Let's say we replace in A:

你可以将它通过允许编译器而不是储备尽可能多的空间,它知道前期——指针和引用,例如,将永远是32或64位(取决于架构),所以如果你更换(任何一个)通过一个指针或引用,事情就太好了。我们用A来代替

// file: A.h
class A {
  // both these are fine, so are various const versions of the same.
  B& _b_ref;
  B* _b_ptr;
};

Now things are better. Somewhat. main() still says:

现在一切都好。有点。main()还说:

// file: main.cc
#include "A.h"  // <-- Houston, we have a problem

#include, for all extents and purposes (if you take the preprocessor out) just copies the file into the .cc. So really, the .cc looks like:

#include,对于所有区段和目的(如果您将预处理器输出),只需将文件复制到.cc中。所以。cc看起来是这样的:

// file: partially_pre_processed_main.cc
class A {
  B& _b_ref;
  B* _b_ptr;
};
#include "B.h"
int main (...) {
  A a;
}

You can see why the compiler can't deal with this - it has no idea what B is - it has never even seen the symbol before.

你可以理解为什么编译器不能处理这个——它不知道B是什么——它以前从来没有见过这个符号。

So let's tell the compiler about B. This is known as a forward declaration, and is discussed further in this answer.

因此,让我们告诉编译器关于b的信息。这就是所谓的前向声明,并在这个答案中进一步讨论。

// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
  A a;
}

This works. It is not great. But at this point you should have an understanding of the circular reference problem and what we did to "fix" it, albeit the fix is bad.

这个作品。这不是伟大。但是在这一点上,您应该了解循环引用问题,以及我们所做的“修复”它,尽管修复是不好的。

The reason this fix is bad is because the next person to #include "A.h" will have to declare B before they can use it and will get a terrible #include error. So let's move the declaration into A.h itself.

这一修复之所以糟糕的原因是因为下一个要做的人包括“A”。h“必须声明B才能使用它,并且会得到一个糟糕的#include错误。让我们把声明变成A。h本身。

// file: A.h
class B;
class A {
  B* _b; // or any of the other variants.
};

And in B.h, at this point, you can just #include "A.h" directly.

和B。在这一点上,你可以只包括“A”。直接h”。

// file: B.h
#include "A.h"
class B {
  // note that this is cool because the compiler knows by this time
  // how much space A will need.
  A _a; 
}

HTH.

HTH。

#2


76  

You can avoid compilation errors if you remove the method definitions from the header files and let the classes contain only the method declarations and variable declarations/definitions. The method definitions should be placed in a .cpp file (just like a best practice guideline says).

如果从头文件中删除方法定义,并让类只包含方法声明和变量声明/定义,则可以避免编译错误。方法定义应该放在.cpp文件中(就像最佳实践指南所说的那样)。

The down side of the following solution is (assuming that you had placed the methods in the header file to inline them) that the methods are no longer inlined by the compiler and trying to use the inline keyword produces linker errors.

下面的解决方案的反面是(假设您已经将这些方法放在头文件中,以内联它们),方法不再由编译器内联,并且试图使用内联关键字产生链接错误。

//A.h
#ifndef A_H
#define A_H
class B;
class A
{
    int _val;
    B* _b;
public:

    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

//B.h
#ifndef B_H
#define B_H
class A;
class B
{
    double _val;
    A* _a;
public:

    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

//A.cpp
#include "A.h"
#include "B.h"

#include <iostream>

using namespace std;

A::A(int val)
:_val(val)
{
}

void A::SetB(B *b)
{
    _b = b;
    cout<<"Inside SetB()"<<endl;
    _b->Print();
}

void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

//B.cpp
#include "B.h"
#include "A.h"
#include <iostream>

using namespace std;

B::B(double val)
:_val(val)
{
}

void B::SetA(A *a)
{
    _a = a;
    cout<<"Inside SetA()"<<endl;
    _a->Print();
}

void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

//main.cpp
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}

#3


15  

Things to remember:

事情要记住:

  • This won't work if class A has an object of class B as a member or vice versa.
  • 如果A类的对象是B类的对象,反之亦然。
  • Forward declaration is way to go.
  • 前进宣言是前进的道路。
  • Order of declaration matters (which is why you are moving out the definitions).
    • If both classes call functions of the other, you have to move the definitions out.
    • 如果两个类都调用另一个类的函数,则必须将定义移出。
  • 声明的顺序很重要(这就是为什么您要将定义移出的原因)。如果两个类都调用另一个类的函数,则必须将定义移出。

Read the FAQ:

阅读FAQ:

#4


11  

I once solved this kind of problem by moving all inlines after the class definition and putting the #include for the other classes just before the inlines in the header file. This way one make sure all definitions+inlines are set prior the inlines are parsed.

我曾经通过在类定义之后移动所有的inlines来解决这类问题,并且在头文件的inlines之前将#include包含到其他类中。这样,确保所有的定义+inlines都被设置在inlines被解析之前。

Doing like this makes it possible to still have a bunch of inlines in both(or multiple) header files. But it's necessary to have include guards.

这样做使得在两个(或多个)头文件中仍然可以有一堆inlines。但有必要包括警卫。

Like this

像这样

// File: A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
    int _val;
    B *_b;
public:
    A(int val);
    void SetB(B *b);
    void Print();
};

// Including class B for inline usage here 
#include "B.h"

inline A::A(int val) : _val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif /* __A_H__ */

...and doing the same in B.h

…在B.h中也一样。

#5


9  

I'm late answering this, but there's not one reasonable answer to date, despite being a popular question with highly upvoted answers....

虽然我很晚才回答这个问题,但迄今为止还没有一个合理的答案,尽管这是一个非常热门的问题

Best practice: forward declaration headers

As illustrated by the Standard library's <iosfwd> header, the proper way to provide forward declarations for others is to have a forward declaration header. For example:

正如标准库的 头文件所示,为其他人提供转发声明的正确方式是有一个转发声明头。例如:

a.fwd.h:

a.fwd.h:

#pragma once
class A;

a.h:

a.h:

#pragma once
#include "a.fwd.h"
#include "b.fwd.h"

class A
{
  public:
    void f(B*);
};

b.fwd.h:

b.fwd.h:

#pragma once
class B;

b.h:

b.h:

#pragma once
#include "b.fwd.h"
#include "a.fwd.h"

class B
{
  public:
    void f(A*);
};

The maintainers of the A and B libraries should each be responsible for keeping their forward declaration headers in sync with their headers and implementation files, so - for example - if the maintainer of "B" comes along and rewrites the code to be...

A和B库的维护人员应该各自负责保持他们的forward声明头与它们的头文件和实现文件同步,因此,例如,如果“B”的维护者出现并重写代码为……

b.fwd.h:

b.fwd.h:

template <typename T> class Basic_B;
typedef Basic_B<char> B;

b.h:

b.h:

template <typename T>
class Basic_B
{
    ...class definition...
};
typedef Basic_B<char> B;

...then recompilation of the code for "A" will be triggered by the changes to the included b.fwd.h and should complete cleanly.

…然后重新编译“A”的代码将由包含的b.fwd的更改触发。h,应该完全干净。


Poor but common practice: forward declare stuff in other libs

Say - instead of using a forward declaration header as explained above - code in a.h or a.cc instead forward-declares class B; itself:

例如,不要使用前面解释的前面的声明头。h或。而cc则是向前声明的B类;本身:

  • if a.h or a.cc did include b.h later:
    • compilation of A will terminate with an error once it gets to the conflicting declaration/definition of B (i.e. the above change to B broke A and any other clients abusing forward declarations, instead of working transparently).
    • 一旦出现了B的冲突声明/定义,A的编译将会终止。B(即上述更改为B,导致A和任何其他客户滥用正向声明,而不是透明地工作)。
  • 如果一个。h或。cc是包括b。h稍后:当A出现冲突声明/定义B的时候,A的编译将会终止。B(即B的上述更改,导致A和任何其他客户滥用正向声明,而不是透明地工作)。
  • otherwise (if A didn't eventually include b.h - possible if A just stores/passes around Bs by pointer and/or reference)
    • build tools relying on #include analysis and changed file timestamps won't rebuild A (and its further-dependent code) after the change to B, causing errors at link time or run time. If B is distributed as a runtime loaded DLL, code in "A" may fail to find the differently-mangled symbols at runtime, which may or may not be handled well enough to trigger orderly shutdown or acceptably reduced functionality.
    • 依赖于#的构建工具包括分析和更改的文件时间戳在更改为B后不会重建A(及其进一步依赖的代码),在链接时或运行时导致错误。如果B是作为一个运行时加载的DLL分配的,那么“a”中的代码可能无法在运行时找到不同的被破坏的符号,这些符号可能或不能很好地处理,从而触发有序关闭或可接受的功能减少。
  • 否则(如果A最终没有包含b)。如果A仅仅是通过指针和/或引用来存储/传递B,那么依赖于#include分析和更改文件时间戳的工具将不会在更改到B之后重新生成A(以及它的进一步依赖的代码),从而在链接时间或运行时造成错误。如果B是作为一个运行时加载的DLL分配的,那么“a”中的代码可能无法在运行时找到不同的被破坏的符号,这些符号可能或不能很好地处理,从而触发有序关闭或可接受的功能减少。

If A's code has template specialisations / "traits" for the old B, they won't take effect.

如果A的代码对旧的B有模板专门化/“特性”,它们就不会生效。

#6


6  

I've written a post about this once: Resolving circular dependencies in c++

我曾经写过这样一篇文章:解决c++中的循环依赖关系。

The basic technique is to decouple the classes using interfaces. So in your case:

基本技术是使用接口将类解耦。所以在你的情况中:

//Printer.h
class Printer {
public:
    virtual Print() = 0;
}

//A.h
#include "Printer.h"
class A: public Printer
{
    int _val;
    Printer *_b;
public:

    A(int val)
        :_val(val)
    {
    }

    void SetB(Printer *b)
    {
        _b = b;
        _b->Print();
    }

    void Print()
    {
        cout<<"Type:A val="<<_val<<endl;
    }
};

//B.h
#include "Printer.h"
class B: public Printer
{
    double _val;
    Printer* _a;
public:

    B(double val)
        :_val(val)
    {
    }

    void SetA(Printer *a)
    {
        _a = a;
        _a->Print();
    }

    void Print()
    {
        cout<<"Type:B val="<<_val<<endl;
    }
};

//main.cpp
#include <iostream>
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}

#7


3  

Here is the solution for templates: How to handle circular dependencies with templates

下面是模板的解决方案:如何处理与模板的循环依赖关系。

The clue to solving this problem is to declare both classes before providing the definitions (implementations). It’s not possible to split the declaration and definition into separate files, but you can structure them as if they were in separate files.

解决此问题的线索是在提供定义(实现)之前声明这两个类。将声明和定义分割为单独的文件是不可能的,但是您可以将它们构建为单独的文件。

#8


2  

The simple example presented on Wikipedia worked for me. (you can read the complete description at http://en.wikipedia.org/wiki/Circular_dependency#Example_of_circular_dependencies_in_C.2B.2B )

*上简单的例子对我很有用。(你可以在http://en.wikipedia.org/wiki/Circular_dependency# example_of_circular_dependencies_in_c . 2b)上阅读完整的描述。

File '''a.h''':

文件“a.h”:

#ifndef A_H
#define A_H

class B;    //forward declaration

class A {
public:
    B* b;
};
#endif //A_H

File '''b.h''':

文件“b.h”:

#ifndef B_H
#define B_H

class A;    //forward declaration

class B {
public:
    A* a;
};
#endif //B_H

File '''main.cpp''':

文件“main.cpp”:

#include "a.h"
#include "b.h"

int main() {
    A a;
    B b;
    a.b = &b;
    b.a = &a;
}

#1


215  

The way to think about this is to "think like a compiler".

思考这个问题的方法是“像编译器一样思考”。

Imagine you are writing a compiler. And you see code like this.

假设您正在编写一个编译器。你会看到这样的代码。

// file: A.h
class A {
  B _b;
};

// file: B.h
class B {
  A _a;
};

// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
  A a;
}

When you are compiling the .cc file (remember that the .cc and not the .h is the unit of compilation), you need to allocate space for object A. So, well, how much space then? Enough to store B! What's the size of B then? Enough to store A! Oops.

在编译.cc文件时(请记住,.cc而不是.h是编译单元),您需要为对象a分配空间,那么,那么,那么,有多少空间呢?足够的存储B !B的大小是多少?足够的存储!哦。

Clearly a circular reference that you must break.

很明显是一个循环的参考,你必须打破。

You can break it by allowing the compiler to instead reserve as much space as it knows about upfront - pointers and references, for example, will always be 32 or 64 bits (depending on the architecture) and so if you replaced (either one) by a pointer or reference, things would be great. Let's say we replace in A:

你可以将它通过允许编译器而不是储备尽可能多的空间,它知道前期——指针和引用,例如,将永远是32或64位(取决于架构),所以如果你更换(任何一个)通过一个指针或引用,事情就太好了。我们用A来代替

// file: A.h
class A {
  // both these are fine, so are various const versions of the same.
  B& _b_ref;
  B* _b_ptr;
};

Now things are better. Somewhat. main() still says:

现在一切都好。有点。main()还说:

// file: main.cc
#include "A.h"  // <-- Houston, we have a problem

#include, for all extents and purposes (if you take the preprocessor out) just copies the file into the .cc. So really, the .cc looks like:

#include,对于所有区段和目的(如果您将预处理器输出),只需将文件复制到.cc中。所以。cc看起来是这样的:

// file: partially_pre_processed_main.cc
class A {
  B& _b_ref;
  B* _b_ptr;
};
#include "B.h"
int main (...) {
  A a;
}

You can see why the compiler can't deal with this - it has no idea what B is - it has never even seen the symbol before.

你可以理解为什么编译器不能处理这个——它不知道B是什么——它以前从来没有见过这个符号。

So let's tell the compiler about B. This is known as a forward declaration, and is discussed further in this answer.

因此,让我们告诉编译器关于b的信息。这就是所谓的前向声明,并在这个答案中进一步讨论。

// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
  A a;
}

This works. It is not great. But at this point you should have an understanding of the circular reference problem and what we did to "fix" it, albeit the fix is bad.

这个作品。这不是伟大。但是在这一点上,您应该了解循环引用问题,以及我们所做的“修复”它,尽管修复是不好的。

The reason this fix is bad is because the next person to #include "A.h" will have to declare B before they can use it and will get a terrible #include error. So let's move the declaration into A.h itself.

这一修复之所以糟糕的原因是因为下一个要做的人包括“A”。h“必须声明B才能使用它,并且会得到一个糟糕的#include错误。让我们把声明变成A。h本身。

// file: A.h
class B;
class A {
  B* _b; // or any of the other variants.
};

And in B.h, at this point, you can just #include "A.h" directly.

和B。在这一点上,你可以只包括“A”。直接h”。

// file: B.h
#include "A.h"
class B {
  // note that this is cool because the compiler knows by this time
  // how much space A will need.
  A _a; 
}

HTH.

HTH。

#2


76  

You can avoid compilation errors if you remove the method definitions from the header files and let the classes contain only the method declarations and variable declarations/definitions. The method definitions should be placed in a .cpp file (just like a best practice guideline says).

如果从头文件中删除方法定义,并让类只包含方法声明和变量声明/定义,则可以避免编译错误。方法定义应该放在.cpp文件中(就像最佳实践指南所说的那样)。

The down side of the following solution is (assuming that you had placed the methods in the header file to inline them) that the methods are no longer inlined by the compiler and trying to use the inline keyword produces linker errors.

下面的解决方案的反面是(假设您已经将这些方法放在头文件中,以内联它们),方法不再由编译器内联,并且试图使用内联关键字产生链接错误。

//A.h
#ifndef A_H
#define A_H
class B;
class A
{
    int _val;
    B* _b;
public:

    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

//B.h
#ifndef B_H
#define B_H
class A;
class B
{
    double _val;
    A* _a;
public:

    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

//A.cpp
#include "A.h"
#include "B.h"

#include <iostream>

using namespace std;

A::A(int val)
:_val(val)
{
}

void A::SetB(B *b)
{
    _b = b;
    cout<<"Inside SetB()"<<endl;
    _b->Print();
}

void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

//B.cpp
#include "B.h"
#include "A.h"
#include <iostream>

using namespace std;

B::B(double val)
:_val(val)
{
}

void B::SetA(A *a)
{
    _a = a;
    cout<<"Inside SetA()"<<endl;
    _a->Print();
}

void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

//main.cpp
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}

#3


15  

Things to remember:

事情要记住:

  • This won't work if class A has an object of class B as a member or vice versa.
  • 如果A类的对象是B类的对象,反之亦然。
  • Forward declaration is way to go.
  • 前进宣言是前进的道路。
  • Order of declaration matters (which is why you are moving out the definitions).
    • If both classes call functions of the other, you have to move the definitions out.
    • 如果两个类都调用另一个类的函数,则必须将定义移出。
  • 声明的顺序很重要(这就是为什么您要将定义移出的原因)。如果两个类都调用另一个类的函数,则必须将定义移出。

Read the FAQ:

阅读FAQ:

#4


11  

I once solved this kind of problem by moving all inlines after the class definition and putting the #include for the other classes just before the inlines in the header file. This way one make sure all definitions+inlines are set prior the inlines are parsed.

我曾经通过在类定义之后移动所有的inlines来解决这类问题,并且在头文件的inlines之前将#include包含到其他类中。这样,确保所有的定义+inlines都被设置在inlines被解析之前。

Doing like this makes it possible to still have a bunch of inlines in both(or multiple) header files. But it's necessary to have include guards.

这样做使得在两个(或多个)头文件中仍然可以有一堆inlines。但有必要包括警卫。

Like this

像这样

// File: A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
    int _val;
    B *_b;
public:
    A(int val);
    void SetB(B *b);
    void Print();
};

// Including class B for inline usage here 
#include "B.h"

inline A::A(int val) : _val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif /* __A_H__ */

...and doing the same in B.h

…在B.h中也一样。

#5


9  

I'm late answering this, but there's not one reasonable answer to date, despite being a popular question with highly upvoted answers....

虽然我很晚才回答这个问题,但迄今为止还没有一个合理的答案,尽管这是一个非常热门的问题

Best practice: forward declaration headers

As illustrated by the Standard library's <iosfwd> header, the proper way to provide forward declarations for others is to have a forward declaration header. For example:

正如标准库的 头文件所示,为其他人提供转发声明的正确方式是有一个转发声明头。例如:

a.fwd.h:

a.fwd.h:

#pragma once
class A;

a.h:

a.h:

#pragma once
#include "a.fwd.h"
#include "b.fwd.h"

class A
{
  public:
    void f(B*);
};

b.fwd.h:

b.fwd.h:

#pragma once
class B;

b.h:

b.h:

#pragma once
#include "b.fwd.h"
#include "a.fwd.h"

class B
{
  public:
    void f(A*);
};

The maintainers of the A and B libraries should each be responsible for keeping their forward declaration headers in sync with their headers and implementation files, so - for example - if the maintainer of "B" comes along and rewrites the code to be...

A和B库的维护人员应该各自负责保持他们的forward声明头与它们的头文件和实现文件同步,因此,例如,如果“B”的维护者出现并重写代码为……

b.fwd.h:

b.fwd.h:

template <typename T> class Basic_B;
typedef Basic_B<char> B;

b.h:

b.h:

template <typename T>
class Basic_B
{
    ...class definition...
};
typedef Basic_B<char> B;

...then recompilation of the code for "A" will be triggered by the changes to the included b.fwd.h and should complete cleanly.

…然后重新编译“A”的代码将由包含的b.fwd的更改触发。h,应该完全干净。


Poor but common practice: forward declare stuff in other libs

Say - instead of using a forward declaration header as explained above - code in a.h or a.cc instead forward-declares class B; itself:

例如,不要使用前面解释的前面的声明头。h或。而cc则是向前声明的B类;本身:

  • if a.h or a.cc did include b.h later:
    • compilation of A will terminate with an error once it gets to the conflicting declaration/definition of B (i.e. the above change to B broke A and any other clients abusing forward declarations, instead of working transparently).
    • 一旦出现了B的冲突声明/定义,A的编译将会终止。B(即上述更改为B,导致A和任何其他客户滥用正向声明,而不是透明地工作)。
  • 如果一个。h或。cc是包括b。h稍后:当A出现冲突声明/定义B的时候,A的编译将会终止。B(即B的上述更改,导致A和任何其他客户滥用正向声明,而不是透明地工作)。
  • otherwise (if A didn't eventually include b.h - possible if A just stores/passes around Bs by pointer and/or reference)
    • build tools relying on #include analysis and changed file timestamps won't rebuild A (and its further-dependent code) after the change to B, causing errors at link time or run time. If B is distributed as a runtime loaded DLL, code in "A" may fail to find the differently-mangled symbols at runtime, which may or may not be handled well enough to trigger orderly shutdown or acceptably reduced functionality.
    • 依赖于#的构建工具包括分析和更改的文件时间戳在更改为B后不会重建A(及其进一步依赖的代码),在链接时或运行时导致错误。如果B是作为一个运行时加载的DLL分配的,那么“a”中的代码可能无法在运行时找到不同的被破坏的符号,这些符号可能或不能很好地处理,从而触发有序关闭或可接受的功能减少。
  • 否则(如果A最终没有包含b)。如果A仅仅是通过指针和/或引用来存储/传递B,那么依赖于#include分析和更改文件时间戳的工具将不会在更改到B之后重新生成A(以及它的进一步依赖的代码),从而在链接时间或运行时造成错误。如果B是作为一个运行时加载的DLL分配的,那么“a”中的代码可能无法在运行时找到不同的被破坏的符号,这些符号可能或不能很好地处理,从而触发有序关闭或可接受的功能减少。

If A's code has template specialisations / "traits" for the old B, they won't take effect.

如果A的代码对旧的B有模板专门化/“特性”,它们就不会生效。

#6


6  

I've written a post about this once: Resolving circular dependencies in c++

我曾经写过这样一篇文章:解决c++中的循环依赖关系。

The basic technique is to decouple the classes using interfaces. So in your case:

基本技术是使用接口将类解耦。所以在你的情况中:

//Printer.h
class Printer {
public:
    virtual Print() = 0;
}

//A.h
#include "Printer.h"
class A: public Printer
{
    int _val;
    Printer *_b;
public:

    A(int val)
        :_val(val)
    {
    }

    void SetB(Printer *b)
    {
        _b = b;
        _b->Print();
    }

    void Print()
    {
        cout<<"Type:A val="<<_val<<endl;
    }
};

//B.h
#include "Printer.h"
class B: public Printer
{
    double _val;
    Printer* _a;
public:

    B(double val)
        :_val(val)
    {
    }

    void SetA(Printer *a)
    {
        _a = a;
        _a->Print();
    }

    void Print()
    {
        cout<<"Type:B val="<<_val<<endl;
    }
};

//main.cpp
#include <iostream>
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}

#7


3  

Here is the solution for templates: How to handle circular dependencies with templates

下面是模板的解决方案:如何处理与模板的循环依赖关系。

The clue to solving this problem is to declare both classes before providing the definitions (implementations). It’s not possible to split the declaration and definition into separate files, but you can structure them as if they were in separate files.

解决此问题的线索是在提供定义(实现)之前声明这两个类。将声明和定义分割为单独的文件是不可能的,但是您可以将它们构建为单独的文件。

#8


2  

The simple example presented on Wikipedia worked for me. (you can read the complete description at http://en.wikipedia.org/wiki/Circular_dependency#Example_of_circular_dependencies_in_C.2B.2B )

*上简单的例子对我很有用。(你可以在http://en.wikipedia.org/wiki/Circular_dependency# example_of_circular_dependencies_in_c . 2b)上阅读完整的描述。

File '''a.h''':

文件“a.h”:

#ifndef A_H
#define A_H

class B;    //forward declaration

class A {
public:
    B* b;
};
#endif //A_H

File '''b.h''':

文件“b.h”:

#ifndef B_H
#define B_H

class A;    //forward declaration

class B {
public:
    A* a;
};
#endif //B_H

File '''main.cpp''':

文件“main.cpp”:

#include "a.h"
#include "b.h"

int main() {
    A a;
    B b;
    a.b = &b;
    b.a = &a;
}