헤더의 순환 참조(Circular dependency)와 전방 선언(forward declaration)
Circular dependency & Forward declarations
헤더 순환 참조는 헤더가 서로를 '#include' 로 순환 참조하는 것을 말한다.
순환 참조 또는 상호 참조 또는 교차 참조라고도 한다.
흔히 발생하는 순환 참조 오류 예시
교차 참조, 순환 참조가 문제를 일으키는 이유 :
- 컴파일 이전 단계인 전처리 단계에서 #include "~.h" 부분에 대한 치환이 이루어진다.
#include "~.h" 부분을 해당 헤더 파일의 내용으로 치환하는 것이다.
그런데 A.h파일에 B.h파일이 include되어 있고, B.h파일에 A.h파일이 include되어있으면 문제가 생긴다.
전처리기가 A.h파일에 있는 #include "B.h"구문을 치환하기 위해
B.h파일에 들어가서 전처리할 내용을 확인하니까 #include "A.h"문구를 발견한다.
해당부분을 치환하기 위해 다시 A.h파일로 갔더니 또 다시 #include "B.h"구문 만난다.
이런 식으로 양쪽 헤더파일의 내용을 치환하기 위해 서로에 대한 교차 재귀가 무한히 중첩되게 된다.
서로가 서로의 헤더를 포함시키고 있어 무한루프가 발생한다.
이 무한 루프를 해결하기 위해 #pragma once 같은 키워드를 사용하면 또 다른 문제가 생긴다.
A.h 파일의 #include "B.h"구문을 치환하려고 B.h 파일의 내용을 살펴보는 과정에서
#pragma once 매크로로 인해서 B.h파일에 #include "A.h"가 사라지게 되고,
그로 인해 B.h파일에서는 A에 대한 내용을 알 수 없어서 오류가 발생한다.
즉, 한쪽 파일을 아예 읽을 수 없게 되버린다.
해결 방법 1 : 전방 선언(forward declaration)
- 전방 선언이란?
대상 타입을 참조를 찾지 못해 오류가 발생하는 것을 피하기 위한 방법이다.
참조하는 위치에서 참조 되는 타입의 signature부분만 참조하는 원리이다.
class A에서 class B를 참조해야한다면,
class B에 대한 전방 선언 코드를 class A 선언 코드보다 위쪽에 위치하도록 하면 된다.
이렇게 하면 전처리 과정에서 일단 해당 타입을 incomplete type으로 놓고
컴파일하게 되기 때문이다.
하지만 위의 코드에서 참조 타입으로 선언 되었던 incomplete type이 참조 타입이 아니라
value type이라면 아래와 같은 오류가 발생한다.
오류 발생 원인은 value type을 incomplete type으로 사용하려고 했기 때문이다.
즉, 포인터로 선언하면 일단 포인터 크기만큼의 공간을 할당하면 되지만,
Value Type으로 선언하면 해당 Type에 할당할 공간의 크기를 미리 알아야 하기 때문이다.
해결 방법 2 : 인터페이스를 이용한 Loose coupling
- 순환 참조하려는 type에 대하여 value type으로 선언하게 될 경우 어떠한 방법으로도 해결할 수가 없다.
아키텍쳐의 설계가 잘못 되었기 때문이다.
이를 해결하는 방법으로 제일 좋은 것은 순환 참조가 일어나지 않게 다시 설계하는 것이다.
대표적으로 상호 참조가 일어나는 부분을 부모 클래스를 이용하여 인터페이스화 하는 것이다.
즉, 공통된 부분을 인터페이스화 하고 인터페이스를 두 클래스에게 상속해주는 방식이다.
참고 :
www.learncpp.com/cpp-tutorial/forward-declarations/
d-yong.tistory.com/64
inpages.tistory.com/157
reakwon.tistory.com/52
gracefulprograming.tistory.com/16
twinw.tistory.com/73
stackoverflow.com/questions/16802485/forward-declaration-circular-dependency