002. C++ 전방선언과 인터페이스 사용 방법

Programming/Old 2016. 6. 24. 19:54

C++ 전방선언과 인터페이스 사용 방법

JavaC#은 인터페이스 사용이 굉장히 편리하게 되어 있다.

그래서 JavaC#을 이용하여 처음 코딩을 시작했던 필자에게 C++의 인터페이스 사용은 굉장히 난해하다.

 

함수 선언부터 되게 이상하다

virtual [반환타입] [함수명] ([매개변수1, 매개변수2, ...]) = 0;

요런 식이다.

이에 비해 JavaC#은 앞뒤의 virtual, =0;은 생략해도 되니 모양이 깔끔하다.

 

이쯤이야 언어의 독특한 특징이니 그렇다 치자.

근데 어째 인터페이스 상속받은 클래스들이 계속 말썽을 부린다. 물론 경각심 없는 필자는 대수롭지 않게 인터페이스를 모두 상속구조로 바꿔버렸다. 그때 좀더 알아봤어야 했는데, 땅을 치고 후회하고 있다.

 

모든 문제는 C++의 선언부와 구현부가 분리되어있는 곳에서 출발한다.

전방선언을 해야하고, 인터페이스가 내 맘처럼 작동하지 않는 것도 모두 이놈 때문이다.

 

C++이 웃긴 게, 이미 한번 컴파일된 헤더파일이 또 나타나면 에러라는 것.

내 맘대로 클래스끼리 참조하고 싶은데 참조가 안 된다. 화딱지가 나서 컴퓨터 때릴 뻔...

 

다행히 이건 전방선언으로 해결했다.

전방선언이란 헤더 파일의 중복은 피하며, 헤더 파일에 선언된 특정 객체의 이름을 사용하기 위한 객체 선언 방식이라고 정의한다면, 어설프지만 어디가서 자신있게 이야기할 수 있을 것 같다.

전방선언의 사용법은 다음과 같다.

 

classA;

classB

{

...블라블라...

}

 

전방선언을 사용하지 않는다면,

#include “A.h”

classB

{

...블라블라...
}

 

전방선언을 사용한다면, B의 헤더파일에서 A의 헤더파일을 포함하지 않고도 A의 이름을 사용할 수 있다.

그럼 A의 기능이 구현되느냐고? 물론 안되지. 구현파일(.cpp)에서는 A의 헤더파일을 당연히 포함해야한다.

 

 

큰 건 하나 해결했지만, 문제는 또 있다.

 

디자인패턴 적용해서 럭셔리하게 코딩하려면 인터페이스는 무조건 사용해야 한다.

맘먹고 인터페이스 사용해야지 하며 예쁘게 인터페이스를 만들고, 이 인터페이스를 구현하는 구현 클래스를 만들었고, 동작까지 시켰다. 근데 한 개는 되는데, 두 개는 안 된다.

 

JavaC# 스타일의 코딩을 원했기 때문에, 각 객체의 인터페이스 파일과 구현 파일을 나눴다.

그렇다면, 만약 Client라는 인터페이스와 이 인터페이스를 구현한 ClientAClientB라는 객체를 만든다고 한다면, Java에서 최종적으로 만들어지는 파일은 Client.class, ClientA.class, ClientB.class일테고,

C#에서는 Client.cs, ClientA.cs, ClientB.cs 일테다.

C++에서 만들면?

당연히 Cllient.h/Client.cpp, ClientA.h/ClientA.cpp, ClientB.h/ClientB.cpp

라고 생각했지만, 당연스럽게도 아니었다. .. 여기서 또 한번 얼굴이 붉어진다.

 

멋있게 팩토리 메소드 패턴 적용해서 간단한 테스트 프로그램 만들어 보려고 했는데, 안된다.

한시간이면 될줄 알았던 스터디가 지금 몇시간 째 끝나지 않고 있단 말이다. 화가 안 나겠어?(ㅠㅠ...)

 

Client.h야 뭐.. 인터페이스 파일이니 구현파일이 따로 존재하지 않아도 된다고 치자.

ClientA의 객체만 만들 때는 잘 된다. 정상적으로 Client를 구현한다. 하지만 복수가 될 때, Client를 구현하는 객체가 두 개 이상일 때는 변수와 메소드를 찾을 수 없다는 에러만 반복해서 뜬다. 물론 문제는 뻔하다. 헤더파일 중복!!!

 

검색을 하여 오버플로우나 다른 블로거들 글을 참조해보지만, 영 맘에 드는 해결책들도 없고, type에 의존하는 방법들이 더러 튀어나오고... 이쯤부터 제대로 찾아보지도 않았다. 뭐라고 검색을 해야할지 몰라서... 부끄럽게도...

 

100% 원하는 형태로 해결되진 않았지만, 어쨌든 해결은 했다.

Client.h 파일 안에 ClientAClientB의 클래스 선언을 두는 것이다. 왜 이렇게 하냐고?

 

만약 ClientAClientB의 클래스의 선언을 별도의 헤더파일로 분리한다면, main 함수에서는 필경 헤더파일이 중복이 발생할 수 밖에 없다. main에서는

#include “ClientA.h”

#include “ClientB.h”

라는 코드로 두 클래스의 헤더파일을 포함할 텐데, ClientA.hClientB.h 각각에는

#include “Client.h”

가 분명 선언되어 있다. , main에서는 제일 꼭대기의 #include “ClientA.h”에서 이미 Client의 헤더파일을 이미 포함했기 때문에, 우리의 멍텅구리 컴파일러께서는 ClientB에서 Client 헤더파일을 컴파일하지 않고 하이패스 해버리신다... 이러면 어떻게 되냐고? 당연히 Client 헤더파일을 포함하지 못한 ClientB는 작동 불능이 되어버리고, 위험천만한 에러들이 출력창에 등장하게 된다.

그러나 이 선언들 모두 Client.h에다 심어(?) 놓으면 main에서는 Client.h만 포함하면 된다. 이제 ClientAClientB의 헤더에 대해서는 깔끔하게 신경 꺼도 된다. 남은 할 일은 ClientAClientB에 해당하는 구현을 ClientA.cpp, ClientB.cpp 파일을 만들어 정의하면 된다는 것! 물론 Client.h 포함하는 것 잊지 말고! 요지는 이거다!

그럼 헤더파일 중복 포함되는데 또 에러 나지 않냐고? 걱정마시라! cpp구현파일에서는 아무리 찌지고 볶아도 에러 안 난다.

 

이 문제는 물론 매크로를 이용해서 해결할 수 있을법 하지만, 매크로를 이용하는 건 영 찜찜하다.

매크로의 이용은 근본적으로 문제를 해결하는 것이 아니라, 컴파일 단계에서 문제를 지나치는 것이기 때문에 성격상 너무 찜찜하고 불쾌하다. 되도록 문제를 컴파일 단계가 아닌 런타임 단계에서 바라봐야한다는 필자의 짧은 소견이며, 이 말은 발생한 문제 자체를 코더, 프로그래머 본인이 제어할 수 있어야 하며, 문제 해결을 위해 머리를 굴리자는 의견이다! (사실... 어떤 매크로가 있는지 귀찮아서 안 찾아봤다는... 이게 어디 런타임 단게에서 발생하는 문제야 - _-;; .... 죄송해요...)


조금만 더 친절을 부려서 아래 실습한 코드 삽입합늬다.

/* File : "Client.h" */
#include <iostream>
using namespace std;

class Client
{
protected:
string str;
public:
void setStr(string mes)
{
str = mes;
}
virtual void getStr() = 0;
};

class ClientA : public Client
{
public:

void getStr();
};

class ClientB : public Client
{
public:

void getStr();
};

지금 보니 Client가 인터페이스라기 보다는 추상 클래스에 가깝지만, 에이.. 도긴개긴 아닙니꺼? 좋은 게 좋은기라. 

/* File : "ClientAImpl.cpp" */
#include "Client.h"

void ClientA::getStr()
{
cout << str.c_str() << endl;
}

/* File : "ClientBImpl.cpp" */
#include "Client.h"

void ClientB::getStr()
{
cout << str.c_str() << endl;
}

각 Client에 대한 구현 정의부.. Impl을 붙이는 건 어쩐지 관행인 것 같네요?

그럼 See you next time~~


ps) 문제의 원인을 잘못 짚었을수도 있으며, 해결방법이 틀렸을지도 모릅니다. 잘못된 부분은 지적해주시고, 궁금한 점은 물어주세요-

admin