상호참조란 서로가 서로를 참조하는 형태를 말한다. 이렇게 될 경우 컴파일러는 무한루프에 빠지게 되며, 최근 똑똑해진 컴파일러는 알아서 중단하고 상호참조를 확인하라는 문구가 뜨지만 예전 컴파일러라면 콘솔창만 뜨고 아무런 행동이 없을 수 있다.
일단 예시를 들어보겠다. 다음은 a클래스와 b클래스를 선언한 헤더파일과 cpp파일들이다.
a.h
#pragma once
#include "b.h"
class cA {
private :
cB m_b;
int iA;
public :
void printB();
void printA();
};
b.h
#pragma once
#include "a.h"
class cB {
private:
cA m_b;
int iB;
public:
void printB();
void printA();
};
a.cpp
#include "stdafx.h"
#include "a.h"
void cA::printB()
{
m_b.printB();
}
void cA::printA()
{
cout << iA << endl;
}
b.cpp
#include "stdafx.h"
#include "b.h"
void cB::printB()
{
cout << iB << endl;
}
void cB::printA()
{
m_a.printA();
}
복잡해보이지만 크게 어려울것은 없다.
a 파일에 b클래스 m_b가 있고 int iA가 있다.
b파일에는 a클래스 m_a가 있고 int iB가 있다.
그리고 함수에 printA,printB함수가 있는데 이는
a클래스에서는 printA는 iA를 출력하고, printB는 b클래스인 m_b를 통해 print B를 호출하여 int iB를 출력한다.
b클래스에서는 printA는 a클래스인 m_iA를 통해 printA를 호출하여 int iA를 출력하고 printB는 iB를 출력한다.
즉 a와 b가 서로 참조를 한 상황이다.
이상태에서 main에서 아무것도 하지 않고 그저 include "a.h"만 하고 메인은 빈 상태로 실행해본다.
main.cpp
#include "stdafx.h"
#include "a.h"
int main(void) {
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
return 0;
}
이 행동은 불가능하다. 비록 a클래스를 선언조차 하지 않았지만. main을 실행하기 위해 main.cpp를 읽기 시작하는데. 맨 위에 include 즉 다른코드보다 우선적으로 처리하고 오라는 명령을 받은 a.h를 읽을것이다.
근데 a.h에는 맨위에 b.h를 읽으라는 명령이 있다.
그래서 b를 갔더니 a.h를 읽고오라는 명령이 또 있다.
이렇게 될 경우 컴파일러는 a와 b를 반복해서 읽으며 무한루프에 빠지게된다.
이것을 방지하기 위하여 전방선언이라는 꼼수를 사용할 수 있다.
전방선언은 그저 빈 클래스를 하나 선언해두는것이다. 즉,
수정된 a.h
#pragma once
class cB;
class cA {
private :
cB* m_pb;
int iA;
public :
void printB();
void printA();
};
이처럼 가장 위에 비어있는 cB를 선언하여 이런 클래스가 있다~~ 라고만 먼저 알려주어 일단 m_b를 선언할 수 있게 해주는데
이렇게만 하고 cB로 m_b를 선언하면 컴파일러가 cB가 어느정도의 크기일지 모르기때문에 오류가 난다.
때문에 cB형 포인터 m_pb를 선언한다. 포인터의 경우 크기가 4바이트로 정해져있기 때문에 cB가 얼마나 크던 상관이 없다.
하지만 결국 cpp파일에서는 진짜 cB가 필요하기때문에 cpp파일에 b.h파일을 include 해준다.
#include "stdafx.h"
#include "a.h"
#include "b.h"
void cA::printB()
{
m_pb->printB();
}
void cA::printA()
{
cout << iA << endl;
}
이렇게 한 후 빌드를 돌리면 아무런 이상이 없다.
main에 cA를 선언하여 printB하더라도 아무런 이상이 없을것이다.
다만 한가지 문제가 생기는 경우가 있다.
지금은 생성자를 만들지 않고 디폴트 생성자로 객체가 생성되었는데
생성자 내에 다른 클래스를 선언하게되면 이또한 문제가 된다.
즉
a.cpp
#include "stdafx.h"
#include "a.h"
#include "b.h"
cA::cA()
{
m_pb = new cB;
}
void cA::printB()
{
m_pb->printB();
}
void cA::printA()
{
cout << iA << endl;
}
이렇게 생성자에 cB를 선언(지금의 경우 할당) 하게 된다면 cA를 어딘가에서 선언하는 즉시 무한루프에 빠진다. 이유를 보자
메인에서 cA를 선언했다.
전방선언을 통해 일단 cB라는 녀석이 있다는건 알았고 cA의 크기를 알았으니 메모리크기를 정해 할당하였고,
cA의 생성자를 실행시키는데
cA에서 cB를 할당하였다.
그러면 cB의 크기를 알아야하는데 그러려면 b.h를 읽어야한다.
b에서는 또 a를 읽고 오라고한다.
이행동이 또 반복되며 무한루프에 빠지게 된다.
이렇기 때문에 이전에 생성자에서 할당하는것은 위험하다고 했고, initialize함수를 만드는 것 또한 이 목적이다.
이렇게 생성자에서 할당하거나 선언하지 않으면 이런일은 벌어지지 않고,
cA와 cB중 둘중 하나에서만 전방선언을 해주면 된다.
그렇다고 상호참조를 하지 않을 상황임에도불구하고 전방선언을 한다면 물론 그래도 동작은 제대로 된다.
하지만 가독성 측면에서 떨어지기 때문에 필요한 상황이 아니라면 사용하지 않길 권장한다.
사실 난 상호참조를 할 일이 생길까?? 라는 의문부터 드는것은 사실이다.
선생님은 엄청 많다는데 잘 모르겠다. ㅋㅋ
이상으로 상호참조를 방지할 전방선언을 마친다.
'c언어' 카테고리의 다른 글
const에 대해서 (0) | 2023.02.13 |
---|---|
클래스 대입 (0) | 2023.02.13 |
클래스 사용 예시 (0) | 2023.02.10 |
클래스의 특징과 기본적 사용방법 (생성자와 소멸자) (0) | 2023.02.10 |
2개월차의 시작, 객체 지향 프로그래밍? (0) | 2023.02.10 |