캡슐화

캡슐화는 데이터(멤버 변수)그 데이터를 다루는 기능(멤버 함수) 을 하나의 클래스 안에 묶는 개념이다.

즉, 클래스의 멤버 변수를 변경하거나 출력하는 함수들을 모두 클래스 내부의 멤버 함수로 작성하는 방식이다.

예시

 
class Test
 
{
 
private:
 
    int m_iX;
 
    int m_iY;
 
public:
 
    void Render();
 
    void SetX_Y(int _iX, int _iY);
 
};
 
  
 
void Test::Render()
 
{
 
    cout << m_iX << endl;
 
    cout << m_iY << endl;
 
}
 
  
 
void Test::SetX_Y(int _iX, int _iY)
 
{
 
    m_iX = _iX;
 
    m_iY = _iY;
 
}
 

은닉화

은닉화는 멤버 변수를 외부에서 직접 접근하지 못하게 막고, 반드시 멤버 함수를 통해서만 접근하도록 제한하는 것이다.

즉, 데이터를 보호하고 잘못된 접근을 막기 위한 개념이다.

예시

 
class Test
 
{
 
private:
 
    int m_iX;
 
    int m_iY;
 
  
 
public:
 
    void Render();
 
    void SetX_Y(int _iX, int _iY);
 
};
 

위 코드에서 m_iX, m_iYprivate이므로 외부에서 직접 접근할 수 없다.  

반드시 SetX_Y() 같은 멤버 함수를 통해 접근해야 한다.


접근 제어 지시자

클래스 멤버에 대한 접근 범위를 제어하는 키워드이다.

  • public

  - 클래스 내부와 외부 모두 접근 가능

  • protected

  - 클래스 내부와 자식 클래스에서 접근 가능

  • private

  - 클래스 내부에서만 접근 가능

일반적인 사용 방식

  • 클래스의 멤버 변수는 private으로 사용

  • 클래스의 멤버 함수는 public으로 사용


상속성

상속은 기존 클래스의 기능과 데이터를 물려받아 새로운 클래스를 만드는 개념이다.

  • 기존 클래스: 기반 클래스, 부모 클래스

  • 새로 만든 클래스: 파생 클래스, 자식 클래스

예시

 
class CObj
 
{
 
public:
 
    CObj() {}
 
    ~CObj() {}
 
  
 
protected:
 
};
 
  
 
class CPlayer : public CObj
 
{
 
public:
 
    CPlayer() {}
 
    ~CPlayer() {}
 
  
 
private:
 
};
 
  
 
int main()
 
{
 
    CPlayer Player;
 
    return 0;
 
}
 

상속 시 주의점

파생 클래스를 선언할 때는 기반 클래스의 헤더 파일을 포함해야 한다.


자식 객체 생성 과정

자식 객체가 생성될 때의 순서는 다음과 같다.

  1. 메모리 할당

  2. 부모 생성자 호출

  3. 자식 생성자 호출


자식 객체 소멸 과정

자식 객체가 소멸될 때의 순서는 다음과 같다.

  1. 자식 소멸자 호출

  2. 부모 소멸자 호출

  3. 메모리 반환


부모와 자식에 같은 이름의 멤버가 있을 때

부모 클래스와 자식 클래스에 같은 이름의 변수가 있다면 이름이 겹치므로 구분해서 접근해야 한다.

예시

 
class CObj
 
{
 
public:
 
    CObj() {}
 
    ~CObj() {}
 
  
 
protected:
 
    int m_iA;
 
};
 
  
 
class CPlayer : public CObj
 
{
 
public:
 
    CPlayer() {}
 
    ~CPlayer() {}
 
  
 
private:
 
    int m_iA;
 
};
 

이 경우 자식 클래스의 m_iA와 부모 클래스의 m_iA가 동시에 존재한다.

부모 클래스의 멤버에 접근하고 싶다면 범위 지정 연산자 :: 를 사용해서 구분할 수 있다.  

현재 객체 자신의 멤버에 접근할 때는 this 포인터를 통해 판단할 수도 있다.


is-a 관계

상속 관계는 보통 is-a 관계 로 표현한다.

즉, 자식 클래스는 부모 클래스의 한 종류이다.

예시

  • 사람 - 남자

  • 동물 - 강아지

즉, 남자는 사람이다, 강아지는 동물이다 와 같이 표현할 수 있다.

자식 클래스로 갈수록 특성이 더 구체화된다.


has-a 관계

has-a 관계는 소유 또는 포함 관계 를 의미한다.

즉, 어떤 객체가 다른 객체를 가지고 있는 관계이다.

예시

  • 플레이어가 무기를 가진다

  • 플레이어가 방어구를 가진다

이런 관계는 상속보다는 멤버 변수로 포함하는 방식 으로 표현하는 것이 적절하다.


객체 포인터 권한

부모 타입 포인터는 자식 객체를 가리킬 수 있다.  

하지만 포인터의 타입 기준으로 접근 가능한 멤버가 결정된다.

예시

 
class CObj
 
{
 
public:
 
    void Render();
 
};
 
  
 
class CPlayer : public CObj
 
{
 
public:
 
    void Render_Player();
 
};
 
  
 
int main()
 
{
 
    CObj* pPlayer = new CPlayer;
 
  
 
    pPlayer->Render();         // 가능
 
    pPlayer->Render_Player();  // 불가능
 
}
 

이유

생성된 객체는 CPlayer이지만, 포인터의 타입이 CObj* 이기 때문에 컴파일러는 CObj에 정의된 멤버만 사용할 수 있다고 판단한다.

즉, CPlayer에만 존재하는 Render_Player()는 호출할 수 없다.


정적 바인딩

정적 바인딩은 컴파일 타임에 어떤 함수를 호출할지 이미 결정되는 것을 말한다.

즉, 호출할 함수가 포인터 타입이나 변수 타입을 기준으로 미리 정해진다.

위 예제에서 CObj* pPlayer를 통해 접근하면 CObj 기준으로만 함수 호출이 결정되는 것도 정적 바인딩의 예이다.


상속의 이점

상속을 사용하면 공통된 기능을 부모 클래스에 모아둘 수 있다.

그래서 여러 객체들을 다음과 같은 방식으로 쉽게 관리할 수 있다.

  • 중복 코드 감소

  • 공통 기능 재사용

  • 일관된 구조 유지

  • 부모 타입으로 여러 자식 객체를 묶어서 관리 가능


다형성

다형성은 하나의 부모 타입으로 여러 자식 객체를 다룰 수 있는 성질이다.

같은 함수 호출이라도 실제 객체가 무엇인지에 따라 다른 동작을 하게 만들 수 있다.

다형성과 관련해서 자주 연결되는 개념은 다음과 같다.


정리

공통적인 기능이지만 세부적인 동작 방식이 다를 때는 오버라이딩 을 사용한다.

하나의 파생 클래스에서만 가지고 있는 고유한 기능이 필요할 때는 다운 캐스팅(dynamic cast) 을 사용한다.

즉, 다형성을 사용할 때는 다음 기준으로 생각하면 된다.

  • 공통 기능의 동작 차이 → 오버라이딩

  • 특정 자식만 가진 기능 사용 → 다운 캐스팅


한 줄 요약

  • 캡슐화: 데이터와 기능을 하나로 묶는다.

  • 은닉화: 데이터를 외부에서 직접 접근하지 못하게 막는다.

  • 상속: 기존 클래스를 기반으로 새로운 클래스를 만든다.

  • 다형성: 부모 타입으로 여러 자식 객체를 다룬다.