소년코딩

클래스 코드와 헤더 파일 (선언과 구현의 분리)

1. 클래스 정의 외부에서 멤버 함수 정의하기

지금까지는 모든 클래스 정의 코드 내에서 바로 멤버 함수를 구현할 정도로 간단한 예제였다.

예를 들어, 아래와 같은 날짜 클래스를 보자:

class Date
{
private:
  int m_Year;
  int m_Month;
  int m_Day;

public:
  Date(int year, int month, int day)
  {
    SetDate(year, month, day);
  }

  void SetDate(int year, int month, int day)
  {
    m_Year = year;
    m_Month = month;
    m_Day = day;
  }

  int GetYear() { return m_Year; }
  int GetMonth() { return m_Month; }
  int GetDat() { return m_Day; }
};

위 예제 코드 정도는 멤버 함수의 양이 적으며, 코드가 복잡하지 않으므로 이해하기 쉽다.

그러나 클래스가 점점 길고 복잡해질 경우(=멤버 함수가 많아질 경우)에는 클래스 관리와 작업이 어려워질 수 있다. 또한, 이미 작성된 클래스를 사용하려면 클래스가 작동 방식이 아닌 공용 인터페이스(공개 멤버 함수)만 이해하면 된다. 멤버 함수의 세부 구현 사항은 방해만 될 뿐이다.

다행히도 C++에서는 클래스 정의 외부에서 클래스 멤버 함수를 정의하는 방법으로 클래스의 '선언''구현'을 분리할 수 있다. 이렇게 하려면 클래스의 멤버 함수를 일반 함수인 것처럼 정의하되, 클래스 이름과 범위 확인 연산자(scope resolution operator) ::을 사용해서 외부에서 멤버 함수를 정의할 수 있다.

아래는 위 Date 클래스의 일부 멤버 함수를 클래스 외부에서 정의한 예제다: (실제 함수는 여전히 클래스 정의 내부에 존재하지만, 실제 구현의 외부에서 한다.)

class Date
{
private:
  int m_Year;
  int m_Month;
  int m_Day;

public:
  Date(int year, int month, int day);

  void SetDate(int year, int month, int day);

  int GetYear() { return m_Year; }
  int GetMonth() { return m_Month; }
  int GetDat() { return m_Day; }
};

// Date 클래스의 생성자
Date::Date(int year, int month, int day)
{
  SetDate(year, month, day)
}

// Date 클래스의 멤버 함수
void Date::SetDate(int year, int month, int day)
{
    m_Year = year;
    m_Month = month;
    m_Day = day;
}

참고로 GetYear()같은 멤버 함수는 코드 길이가 한 줄밖에 되지 않으므로 클래스 정의에 남겨두는 게 일반적이다.

아래는 멤버 초기화 리스트를 사용하는 생성자가 있는 또 다른 예제다:

class Calc
{
private:
    int m_value = 0;

public:
    Calc(int value=0): m_value(value) {}

    Calc& add(int value) { m_value  += value;  return *this; }
    Calc& sub(int value) { m_value -= value;  return *this; }
    Calc& mult(int value) { m_value *= value;  return *this; }

    int getValue() { return m_value ; }
};

분리 후:

class Calc
{
private:
    int m_value = 0;

public:
    Calc(int value=0);

    Calc& add(int value);
    Calc& sub(int value);
    Calc& mult(int value);

    int getValue() { return m_value; }
};

Calc::Calc(int value): m_value(value)
{
}

Calc& Calc::add(int value)
{
    m_value += value;
    return *this;
}

Calc& Calc::sub(int value) 
{
    m_value -= value;
    return *this;
}

Calc& Calc::mult(int value)
{
    m_value *= value;
    return *this;
}

2. 클래스 정의 헤더 파일 만들기

이전 포스트 헤더 파일에서 여러 파일 또는 여러 프로젝트에서 함수 선언을 사용하기 위해 헤더 파일을 작성하는 방법을 배웠다. 클래스 또한 다르지 않다. 여러 파일 또는 여러 프로젝트에서 쉽게 재사용할 수 있도록 클래스를 헤더 파일에 넣을 수 있다. 일반적으로 클래스 정의는 클래스와 이름이 같은 헤더 파일(.h)에, 클래스 외부에 정의된 멤버 함수는 클래스와 이름이 같은 .cpp 파일에 넣는다.

이 방식으로 선언과 구현을 분리할 수 있다.

Date.h:

class Date
{
private:
  int m_Year;
  int m_Month;
  int m_Day;

public:
  Date(int year, int month, int day);

  void SetDate(int year, int month, int day);

  int GetYear() { return m_Year; }
  int GetMonth() { return m_Month; }
  int GetDat() { return m_Day; }
};

Data.cpp:

#include "Date.h"

// Date 클래스의 생성자
Date::Date(int year, int month, int day)
{
    SetDate(year, month, day);
}

// Date 클래스의 멤버 함수
void Date::SetDate(int year, int month, int day)
{
    m_Month = month;
    m_Day = day;
    m_Year = year;
}

이제 Data 클래스를 사용하는 다른 헤더 또는 코드 파일은 간단히 #include Date.h를 포함시키기만 하면 된다. (주의: Date.cpp 또한 링커가 Date가 어떻게 구현됐는지 알 수 있기 위해 Date.h를 포함시켜야 한다. )

기본 매개 변수 (Default Parameter)

멤버 함수의 기본 매개 변수(default parameter)는 클래스 정의(헤더 파일)에 선언되어야 한다.

Calc.h:

class Calc
{
private:
    int m_value = 0;

public:
    // 기본 매개 변수(default parameter)는 클래스 정의(헤더 파일)에 선언되어야한다. 
    Calc(int value=0);

    //...
};

3. 라이브러리와 cpp 파일

iostream.h, string.h, vector.h와 같은 표준 라이브러리는 모두 클래스 정의와 구현이 분리되어있다. 컴파일러는 문법적으로 올바른 프로그램이 작성되었는지 확인하기 위해 헤더 파일의 선언만 필요로 하므로 iostream.cpp와 같은 파일은 프로젝트에 추가할 필요가 없다. 이처럼 C++ 표준 라이브러리에 속하는 클래스에 대한 구현(cpp 파일)은 링크 단계에서 링크된 미리 컴파일된 파일에 포함되어있으므로 구현 소스 코드를 볼 수 없다.

.h 및 .cpp가 모두 제공되는 일부 오픈 소스 코드를 제외한 대부분의 타사 라이브러리는 미리 컴파일된 라이브러리 파일과 함께 헤더 파일만 제공한다. 그 이유는:

  1. 필요할 때마다 다시 컴파일하는 것보다 미리 컴파일된 라이브러리 연결하는 것이 더 빠르다.
  2. 미리 컴파일된 라이브러리는 다른 많은 애플리케이션에 의해 공유될 수 있다. (컴파일된 코드는 그 프로그램에서만 사용할 수 있다.)
  3. 지적 재산권 보호 (코드를 못 훔치도록..)

자신의 코드를 선언(헤더)과 구현(코드 파일)으로 분리하는 것은 관리와 작업에 있어 좋은 형태일 뿐만 아니라, 자신만의 라이브러리를 쉽게 만들 수 있다. 자신만의 라이브러리를 만드는 것은 이 포스트의 범위를 벗어나지만, 선언과 구현을 분리하는 것은 그렇게 하기 위한 필수 조건이다.


cpp 번역: 이 포스트의 원문은 https://www.learncpp.com/cpp-tutorial/89-class-code-and-header-files/ 입니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기