전방선언(Forward Declaration)에 대해서 적어 본다.
나는 C++은 기초 정도만 알아놓고 바로 Qt로 와서, 전방선언에 대해서 잘 알지 못하였는데 말이다. 그런데 예전에 Qt를 이것저것 공부하면서 검색도 하고, 남의 소스코드를 보니 대부분 어떤 클래스 인스턴스를 생성할 때 요런 식으로 하는 것이었다.
something.h 헤더 파일
[code]
...
public :
QPushbutton *btn;
...
[/code]
something.cpp 소스 파일
[code]
...
btn = new QPushbutton(...);
...
[/code]
보면서 이해가 잘 가지 않았다. 굳이 포인터로 선언을 할 필요가 있을까? 이렇게 포인터로 선언해 놓으면 소스코드 부분에서 다시 new로 할당을 해 줘야 사용이 가능하게 되는데 이러면 두 번을 작업하는 거니까 오히려 귀찮게 보이기도 하고 말이다. 그래도 korone.net이나 Qt Assistant의 예제들도 이런 스타일로 선언하고 사용하길래 나도 아무 생각 없이 이렇게 사용하고는 했다.
그러던 어느 날, Qt Assistant의 예제를 계속 살펴보았는데 이상한 점이 있었다. 예전에 발견하지 못했던 것이 이제와서 눈에 보이기 시작한건데... 위의 something.h에서 한 줄을 더 발견한거다.
새롭게 보이게 된 something.h
[code]
class QPushButton;
...
public :
QPushbutton *btn;
...
[/code]
바로 1라인이 문제의 그 라인이었다. 도대체 저렇게 선언하는 이유를 알 수가 없었던 것이다. 저렇게 달랑 해놓으면 무슨 소용이지? 거기다 더 웃겼던 문제는 저 헤더 파일에는 #include <QPushButton>, 인클루드를 하는 부분이 없기 때문에 그야말로 빈 껍데기일 뿐이었다. 진짜 혼란이었다. 더욱 큰 문제는, 저런 스타일의 선언을 보았을 때 신기하니까 검색해 볼만도 한데, 나는 저게 무슨 뜻인지도 몰랐고, 검색어로 뭐를 써야할지 난감했던 것이다. 그래서 검색을 하지도 않고, 저런 식으로 선언은 시도조차 하지 않았는데...
지금부터 반년쯤 전인가, 사당이었나 이수였나 하는 큰 서점에 Effective C++이란 책을 사려고 간 적이 있었다. 가서 이런저런 책도 읽어볼겸 해서 여러 책을 읽어 보았는데 개중에 Qt4를 이용한 GUI 프로그래밍이었나 하는, 초록색 분위기의 정식 번역서 따위의 수식어가 붙은 책을 보게 되었다. 예전에 열혈강의 Qt라는 책을 샀었는데, 이 책에는 상당히 실망했던 터라(korone.net에 저자분 가끔 보이시던데, 죄송합니다. 근데 책은 진짜 별로였어요) 그 초록색 책은 어떤가 싶어서 읽어 보았다. 한장 한장 빠르게 넘기며 보다가 깜짝 놀랐는데, 위에 쓴 스타일의 선언법에 대한 명칭이 나와 있었다. 전방선언(Forward declaration)이라고 했다.
그 뒤로 집에 와서는 전방선언에 대해서 찾아 보았다. 정말 보면서 바보같다고 느꼈던 것이, 예전에 Qt를 배우기 시작할 때 봤던 내용이었는데 잘 보려고 하지도 않았었고, 또 뭔지도 몰라서 넘어갔었던 것이었다. 학교에서 배우던 C++에서는 전방선언같은건 가르쳐주지도 않으니까 C++단계에서는 찾아보지 않는 이상 알 수가 없었고;
크게 보면 전방선언에는 2가지의 장점이 있는 것 같다.
1. 선언 크기의 고정
char 형 변수로 aa를 선언했다고 치자. 그러면 이 변수의 사이즈는? 1바이트가 될 것이다. 그러면 int bb는? 당연히 4바이트가 될 것이다. 그러면 QPushButton cc의 크기는? 이건 알 수가 없다. QPushButton의 헤더를 참고하여 그 크기를 계산해 봐야 할 것이다.
반면 char *aa일 경우는 그 크기가 4바이트가 된다. 어라? 1바이트보다 더 큰데? 그런데 QPushButton *cc의 경우는 4바이트라고 확정지을 수 있다. 왜냐 하면 포인터 변수는 (대부분)4바이트니까 말이다. 별다른 참고 없이, 별다른 계산 없이 알 수가 있는 것이다.
다만 밑도끝도 없이 QPushButton *cc라고 선언을 하면 컴파일 단계에서 에러가 난다. 컴파일러 입장에서는 이게 뭔지 전혀 알 수가 없는데 냅다 컴파일 하라니 당연히 그렇게 된다. 그래서 전방선언 class QPushButton;을 통해서 이런 클래스가 있다는 것을 알려주게 되는 것이다. 엥? 알려주기만 해서 그게 사용이 가능하다는건가? 그 안에 내용이 뭔지 하나도 모르는데?
그래서 #include <QPushButton>는, 실제로 이 QPushButton이 사용될 CPP파일에 추가한다.
2. 컴파일 시간을 단축
make따위의 프로그램으로 컴파일을 하다 보면 신기하게도 변한 것이 있을 때만 컴파일을 한다. 더 놀라운 것은, 프로젝트 내에서 이전과 비교해서 내용이 변한 파일만 컴파일을 한다는 것이다. 내용이 변했다고 한다면 소스코드의 한줄 추가하고 삭제하고 이런 레벨에서 생각할 수도 있겠지만, 이 소스코드가 바뀌지 않아도 컴파일을 다시 해야 할 때가 있다. 그게 어느 때냐 하면, 그 소스코드에서 인클루드를 한 경우다. 예를 들어 something.h와 something.cpp에서 둘 다 header.h를 인클루드하는데, 이 header.h의 내용이 바뀌면? 당연히 내용이 한 개도 안바뀌었어도 이 header.h를 포함한 something.* 파일들을 컴파일해야 하는 것이다. 문제는 header.h를 포함하는 파일이 아주 많은데 수정한 부분은 얼마 되지도 않다면, 상당히 비효율적인 컴파일이 되는 것이다.
그런데 전방 선언을 통해서 클래스를 선언해 놓으면? #include를 하지 않았기 때문에 적어도 전방선언이 된 헤더 파일은 컴파일을 할 필요가 없다. 어차피 뭔지도 모르고 참고도 하지도 않았으니까. 복잡한 의존성 때는 더 효율적으로 컴파일이 되는 것이다.
뭐 이런 이유로 전방선언을 한다고 한다. 근데 문제는 나는 이런 식으로 작성하고 있지 않았다는 것. 앞으로는 신경 좀 써서 작성해야 할 듯 하다. 내가 참고해야 할 팁은 많고, 내 프로그램은 아직도 개선해야 할 여지가 많다는 것.
맙소사 너무 좋은 글 감사합니다.
답글삭제