SW 그리고 아빠 엔지니어링 중...

아는 만큼 보이고, 경험해봐야 알 수 있고, 자꾸 써야 내 것이 된다.

L C++

직접선언! 낄낄빠빠하자.

보리남편 김 주부 2024. 9. 9. 01:44
💬 최신 소스로 당겨 빌드를 하는데 빌드에러가 났다.
원인은 헤더파일에 다른 헤더파일을 직접 참조한 게 원인이었다.
//A.h
#include "B.h" // 직접참조로 인한 error

에러는 누구나 발생시킬 수 있지만 직접참조전방선언 차이에 대해 제대로 이해를 못 하면 유사한 이슈가 재발하기에 이참에 정리해 본다.

 

직접참조? 와 전방선언


우선 직접참조와 전방선언의 모양을 임의의 MyClass 클래스를 예로 확인해 보자.

// MyClass.h
class MyClass {
public:
    void someFunction();
    int someVariable;
};

 

직접 선언(Direct declaration)의 예

#include "MyClass.h" //직접선언

int main() {
    MyClass obj;
    obj.someFunction();
    return 0;
}

사실 직접참조라는 용어는 없고, 헤더파일에 MyClass 전체가 포함되어 있다면 직접 선언이 포함된 것이다.

 

전방선언(Forward declaration)의 예

class MyClass;  // 전방 선언
MyClass* ptr = new MyClass();  // 올바른 사용 
이 둘의 차이는 단순히 모양만 다른 것이 아니다.

 

직접 선언을 하게 되면

  • 코드 수정 시 이를 포함하는 모든 파일을 다시 컴파일한다.
  • 여러 헤더파일을 직접 선언하면서 '순환 의존 오류', 같은 클래스의 함수가 중복 선언되는 '심볼 재정의 오류'가 발생할 수 있다.

하지만 전방 선언을 하게 되면

  • 헤더 간 순환참조를 방지하여 컴파일 속도를 향상 시킨다.
💬 "이렇게 얘기하면 전방 선언만 해야 할 것 같은데?"란 생각을 할 수도 있는데 전방 선언을 사용할 수 없는 상황이 있다.
class MyClass;  // 전방선언

MyClass* ptr = new MyClass();  // 컴파일 가능
// ptr->someFunction();  // 컴파일 에러
// int x = ptr->someVariable;  // 컴파일 에러

전방 선언 자체로는 메모리 할당도 하지 않고, 동적 메모리 할당도 런타임에 발생하기에 컴파일 속도가 빠르지만,
멤버함수나 변수 호출은 완전한 클래스가 정의된 이후에나 가능하다.

 

💬 "이렇게 얘기하면 전방 선언을 어떻게 사용해?"란 생각을 할 수도 있는데 클래스 설계에 따라 사용 방법이 달라지니 확인해 보자.

 

직접 선언을 해야 하는 경우

//UsingClass.h
#include "MyClass.h" //직접 선언

class UsingClass : public MyClass { //1. 상속
public:
 MyClass mc; // 2. 객체 생성

public:
 someFunction() {
  mc.someFunction(); // 3. 멤버함수 호출
 };
 MyClass getFunction(); //4. MyClass 리턴
}

 

완전한 클래스 정의가 되어 있어야 위 4가지가 가능하기에 이런 케이스에는 직접 선언을 사용해야 한다.

 

전방 선언 사용형태

//UsingClass.h

class MyClass;  // 전방선언

class UsingClass {
MyClass *mc;
public:
 someFunction(MyClass *mc);

//UsingClass.cpp
#include "MyClass.h" //직접선언

UsingClass::someFuction(MyClass *mc){
 mc = new MyClass();
}

 

하지만 이 코드처럼 UsingClass의 정의 시 MyClass가 굳이 정의되어 있을 필요가 없다면, 이 처럼 cpp파일에서 직접선언을 하고 헤더에서는 전방선언으로 순환참조를 막고 컴파일러 속도를 향상하는 방법으로 설계를 하길 바란다.

 

 

 

728x90