내가 프로젝트 ++은 C 여러 컴파일 / 링커 오류에 직면하고 어디 인해 종종 다른 헤더 파일의 원형 C 간의 종속성 ++ 클래스로 이어질 나쁜 디자인 결정 (다른 사람이 만든 :))에 상황에서 자신을 찾을 (도 일어날 수있다 같은 파일에) . 그러나 다행히도 (?) 이것은 다음에 다시 발생할 때이 문제에 대한 해결책을 기억하기에 충분하지 않습니다.
앞으로 쉽게 리콜 할 수 있도록 대표적인 문제와 해결 방법을 함께 게시하겠습니다. 더 나은 솔루션은 물론 환영합니다.
-
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
#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
#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; }
답변
이것을 생각하는 방법은 “컴파일러처럼 생각하는 것”입니다.
컴파일러를 작성한다고 가정하십시오. 그리고 당신은 이와 같은 코드를 볼 수 있습니다.
// 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;
}
.cc 파일을 컴파일 할 때 ( .h 가 아닌 .cc 가 컴파일 단위 임을 기억하십시오 ) object에 대한 공간을 할당해야합니다 . 그렇다면 공간이 얼마나됩니까? 저장하기에 충분합니다 ! 그때 의 크기는 얼마입니까? 저장하기에 충분합니다 ! 죄송합니다.A
B
B
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;
};
이제 상황이 더 좋습니다. 약간. main()
여전히 말한다 :
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
, 모든 범위와 목적을 위해 (전처리기를 꺼내는 경우) 파일을 .cc에 복사하기 만하면 됩니다. 실제로 .cc 는 다음과 같습니다.
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
컴파일러가이 문제를 처리 할 수없는 이유를 알 수 있습니다. 그게 무엇인지 전혀 모릅니다 B
. 이전에는이 기호를 본 적이 없습니다.
따라서 컴파일러에 대해 알려주십시오 B
. 이것을 순방향 선언 이라고 하며이 답변 에서 자세히 설명 합니다.
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
이 작동합니다 . 대단 하지 않습니다 . 그러나이 시점에서 순환 참조 문제와 문제를 “수정”하기 위해 수행 한 조치를 이해해야합니다.
이 수정이 잘못된 이유는 다음 사람이 이를 사용하기 전에 #include "A.h"
선언 B
해야하고 끔찍한 #include
오류가 발생하기 때문입니다. 선언을 Ah 자체 로 옮깁니다 .
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
그리고에서와 BH ,이 시점에서, 당신은 할 수 #include "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.
답변
헤더 파일에서 메소드 정의를 제거하고 클래스에 메소드 선언 및 변수 선언 / 정의 만 포함 시키도록하면 컴파일 오류를 피할 수 있습니다. 방법 정의는 모범 사례 지침과 같이 .cpp 파일에 배치해야합니다.
다음 솔루션의 단점은 메소드가 더 이상 컴파일러에 의해 인라인되지 않고 인라인 키워드를 사용하려고하면 링커 오류가 발생한다는 것입니다 (헤더 파일에 메소드를 인라인하기 위해 가정 한 경우).
//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;
}
답변
나는 이것에 늦게 대답하고 있지만, 고도로 찬성 한 답변으로 인기있는 질문이지만 그럼에도 불구하고 현재까지 합리적인 대답은 하나도 없습니다 ….
모범 사례 : 전달 선언 헤더
표준 라이브러리의 <iosfwd>
헤더에서 알 수 있듯이 다른 사람에게 전달 선언을 제공하는 올바른 방법은 전달 선언 헤더 를 갖는 것 입니다 . 예를 들면 다음과 같습니다.
a.fwd.h :
#pragma once
class A;
아 :
#pragma once
#include "a.fwd.h"
#include "b.fwd.h"
class A
{
public:
void f(B*);
};
b.fwd.h :
#pragma once
class B;
bh :
#pragma once
#include "b.fwd.h"
#include "a.fwd.h"
class B
{
public:
void f(A*);
};
의 메인테이너 A
및 B
예를 들어 – – 라이브러리는 각 그래서, 자신의 헤더와 구현 파일과 동기화 자신의 앞으로 선언 헤더를 유지하기위한 책임을 져야한다 “B”의 메인테이너가 따라오고 코드로 재 작성하는 경우 …
b.fwd.h :
template <typename T> class Basic_B;
typedef Basic_B<char> B;
bh :
template <typename T>
class Basic_B
{
...class definition...
};
typedef Basic_B<char> B;
… 그런 다음 “A”에 대한 코드 재 컴파일은 포함 된 변경 사항에 의해 시작되며 b.fwd.h
완전히 완료되어야합니다.
가난하지만 일반적인 관행 : 다른 라이브러리에서 전달하는 내용
위에서 설명한대로 전달 선언 헤더를 사용하는 대신 코드 자체 를 전달 a.h
하거나 a.cc
대신 선언하십시오 class B;
.
- 경우
a.h
또는a.cc
포함 않았다b.h
이상 :- A의 컴파일은 충돌 선언 / 정의에 도달하면 오류와 함께 종료됩니다
B
(즉, 위의 B 변경으로 인해 A와 투명하게 작업하는 대신 선언을 남용하는 다른 클라이언트).
- A의 컴파일은 충돌 선언 / 정의에 도달하면 오류와 함께 종료됩니다
- 그렇지 않으면 (A가 결국 포함하지 않은 경우-A가
b.h
포인터 및 / 또는 참조로 B 주위에 저장 / 전달하는 경우 가능)#include
분석에 의존하는 빌드 도구 및 변경된 파일 타임 스탬프는A
B로 변경 한 후 다시 빌드되지 않으며 링크 타임 또는 런타임시 오류가 발생합니다. B가 런타임로드 DLL로 배포되는 경우 “A”의 코드는 런타임에 다르게 얽힌 기호를 찾지 못할 수 있습니다.이 기호는 순서대로 종료되거나 기능이 상당히 저하 될 정도로 충분히 처리되지 않을 수 있습니다.
A의 코드에 old의 템플릿 전문화 / “특성”이있는 경우 B
적용되지 않습니다.
답변
기억해야 할 것 :
- 멤버로서의
class A
오브젝트가class B
있거나 그 반대 의 경우 에는 작동하지 않습니다 . - 앞으로 선언하는 것이 좋습니다.
- 선언 순서가 중요합니다 (이것이 정의를 옮기는 이유입니다).
- 두 클래스가 다른 클래스의 함수를 호출하면 정의를 밖으로 이동시켜야합니다.
FAQ를 읽으십시오 :
답변
나는 한 번에 이런 종류의 문제를 해결하여 클래스 정의 후 인라인 하고 헤더 파일 #include
의 인라인 바로 앞에 다른 클래스를 배치하여 . 이렇게하면 인라인을 구문 분석하기 전에 모든 정의 + 인라인을 설정해야합니다.
이렇게하면 둘 다 (또는 여러 개의) 헤더 파일에 여전히 많은 인라인을 가질 수 있습니다. 그러나 경비원 을 포함 해야합니다 .
이렇게
// 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__ */
… 그리고 같은 일을 B.h
답변
나는 이것에 대해 한 번 글을 썼습니다 : C ++에서 순환 종속성 해결
기본 기술은 인터페이스를 사용하여 클래스를 분리하는 것입니다. 따라서 귀하의 경우 :
//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;
}
답변
템플릿 솔루션은 다음과 같습니다. 템플릿 을 사용하여 순환 종속성을 처리하는 방법
이 문제를 해결하는 단서는 정의 (구현)를 제공하기 전에 두 클래스를 모두 선언하는 것입니다. 선언과 정의를 별도의 파일로 분할 할 수는 없지만 마치 별도의 파일에있는 것처럼 구성 할 수 있습니다.