4 분 소요

OOP(Object-Oriented Programming)에 대하여

관련 포스팅 + Solid

OOP의 특징

객체 지향 프로그래밍‘으로
데이터와 그 데이터를 다루는 동작(함수)를 하나의
객체로 묶어 설계하는 방식

장점

  • OOP는 ‘현실’에 존재하는 특정한 물체 표현에
    적합하기에 직관적임
    • 예를 들어 물뿌리개 객체를 만든다면
      • 데이터 : 현재 물의 양, 뿌리는 양, 총 물의 양 등을 정의
      • 동작 : 물 채우기, 물 뿌리기 등을 정의
  • 잘 설계된 OOP는 유지보수와 확장성을 모두 챙길 수 있음
    • ‘플레이어’가 ‘도구’를 사용할 수 있을 때
      • 그 ‘도구’ 클래스를 상속 받은 후,
        새로이 ‘망치’ 클래스를 제작하면
        ‘플레이어’와 ‘도구’ 클래스는 건들지 않고
        신규 기능 추가 가능

단점

  • 설계가 복잡할 수 있음
    • 물뿌리기는 통과 스프레이 부분을 나눠서 설계해야 하나??
    • 뿌리는 대상을 어떻게 정의할까?
    • 물도 하나의 객체로 정의해야 하나?
  • 추상화의 성능 오버헤드
    • 함수 호출 역시 기본적으론 추가 명령
    • 동적 바인딩 & 간접 참조 등의 비우호적인 캐시 구조
  • 잘못된 설계의 경우는 오히려 디버깅 난이도의 증가
    • 어떤 ‘클래스/함수’에서 호출하는지 추적이 어려운 경우 등
      유지보수와 직관성이 떨어지기에 잘못 쓰면 안쓰는 것보다 못함

OOP의 핵심 요소

  • 추상화
    • 중요한 것만 외부에 노출, 세부 구현은 숨김
    • 구현의 복잡성을 줄어들게 함
    • 순수 가상 함수, Interface 스타일을 통해 추구
  • 캡슐화
    • 데이터와 행동을 묶으며, 외부 접근 제한
    • 내부 구조 은닉 및 객체 상태 보호
    • 접근 제어자, Const, Friend 키워드를 C++이 지원
  • 상속
    • 기존 클래스를 기반으로 신규 클래스를 정의해 기능 확장
    • 중복 코드를 제거하여 코드 재사용성 증가
    • 상속 기능 및 상속 지정자 를 C++이 지원
  • 다형성
    • ‘같은’ 호출이지만 동작은 다르게 구현
    • 자식 클래스들의 유연성을 증가
    • Virtual, Override, Overloading 등의 기능 존재

C++에서 OOP를 지원하는 기능 및 개념

  • 클래스
    객체의 설계도
    • 실제 객체를 생성하기 위한 ‘틀’ 같은 개념으로 인식
    • C++에서 class 키워드를 기반으로 선언
      (Class vs Struct?)
  • 객체
    클래스를 기반으로 생성된 것
    (실제 메모리에 올라간 것)
    • New 키워드 등을 통해 Heap 영역에 할당되거나
      멤버, 지역 변수 등 다양한 곳에서 쓰인다
      (New Vs Malloc?)
  • 접근 제어자
    • Public : 누구나 접근 가능
    • Protected : 자식 클래스는 접근 가능
    • Private : 해당 클래스만 접근 가능
      • 다만 예외로 Friend 선언한 클래스는
        private 키워드가 선언된 곳도 접근이 가능하다
        (그렇기에 Friend 키워드를 캡슐화를 망칠 수 있는 요소라 부르기도?)
  • 상속
    • 상속 지정자를 통해
      부모 클래스의 기능을 이어 받음
    • 아래 코드의 예시로, Derived 클래스는 Base의 모든 기능을 포함한다
    • 상속 지정자를 통해 여러 옵션이 존재
      • public : 부모의 접근 제어자를 그대로 받음
      • protected : 부모의 public을 protected로 변경하여 받음
      • private : 부모의 모든 요소를 private로 변경하여 받음
class Base { };

class Derived : public Base {
};

  • Virtual 키워드
    • 가상 함수를 선언하는 키워드
    • 해당 키워드 사용한 함수를 포함하는 객체는
      ‘가상 함수 테이블’을 포함하여 생성됨
      (그래서 크기가 약간 늘어남)
    • Virtual 이 없으면 ‘정적 바인딩’
      있으면 ‘동적 바인딩’이라 부를 수 있음
  • override 키워드
    • 가상 함수를 ‘새로 정의’하는 용도의 키워드
    • 다만, 실제로 동작을 하기 보다는 가독성의 측면에 가까운 편
      (없어도 똑같이 동작은 되지만, 있으면 ‘상속 받은 가상 함수’라는 가독성이 생김)
    • 또한 virtual 함수가 아니라면 컴파일 오류를 발생시켜주기에 안정성이 높아짐
  • 동적 & 정적 바인딩?
    • 정적 바인딩 : 컴파일 타임에 호출할 함수 결정
      (보통 캐스팅한 포인터 타입에 따라 함수를 호출함)
    • 동적 바인딩 : 런타임에 실제 객체 타입 확인 후, 호출 함수 결정
      (동적 바인딩이 vtable을 사용)
  • 순수 가상 함수
    • 구현이 없는 가상 함수
      (ex : virtual a() = 0;)
      (=0 부분을 매크로 등으로 선언하여 사용하기도 함)
    • 자식 클래스에서 오버라이딩을 강제
      (구현하지 않을 경우, 그 클래스도 추상 클래스가 된다)
    • 인터페이스 등에서 자주 사용하는 방식으로
      특정 기능에 대한 구현을 요구하는 용도로 응용 가능
  • 추상 클래스
    • 하나 이상의 ‘순수 가상 함수’를 포함하는 클래스
    • 인스턴스 생성이 불가함
      (생성 시도시, 컴파일 에러 발생)
      (C2259 - 추상 클래스를 인스턴스화할 수 없음!)
// 인터페이스 형식
class IDamagable
{
public:
  virtual void Damage() = 0;
}

class Actor : public IDamagable
{
  // Damage 미 구현시 Actor 클래스는 인스턴스화 불가 -> 구현 강제를 통한 기능 부여
}
  • 오버로딩
    • 같은 이름의 함수/연산자를 매개변수를 다르게 하여 ‘여러 개’ 만드는 것
    • 리턴 타입만 다른 경우는 생성 불가
void Print(int x);
void Print(double x);
void Print(const string& x);
void Print(int x, int y);

int Foo();  
double Foo();  // ❌ 리턴 타입만 다르면 구분 안 됨
  • friend 키워드
    • 특정 함수/클래스가 해당 클래스의 private/protected 영역에 접근 가능하도록 함
    • 특별한 예외 처리이며, 강력하지만 동시에 OOP 원칙을 해치기에 유의하여 사용해야함
      • 연산자 오버로딩 or ‘밀접’한 관계를 만들때 사용 가능
      • ‘정말 필요한 경우’에만 사용을 권장하는 편
class A {
private:
    int secret = 10;

    friend void Debug(A& a); // 전역 함수
    friend class B; // B 클래스 전체
    friend void C::Print(A&); // C 클래스의 일부 함수
};

//

void Debug(A& a) {
    cout << a.secret;  // ✔ 접근 가능!
}

class B{
  void Test(A a)
  {
    a.secret += 50;// ✔ 접근 가능!
  }
}

TMI - 소멸자에 Virtual을 붙여야 하는 이유?

class Base {
public:
    ~Base() { cout << "Base dtor\n"; }
};

class Derived : public Base {
public:
    ~Derived() { cout << "Derived dtor\n"; }
};

Base* ptr = new Derived();
delete ptr;
  • 다음과 같은 경우
    상속받은 Derived의 소멸자는 호출되지 않음!

  • 업캐스팅한 경우, 쉽게 발생 가능
    • 업캐스팅 : 부모 클래스의 포인터로 자식 객체를 가리킴
    • Derived* 로 참조했다면, 양 쪽 모두 호출됨
  • 생성자는 부모 - 자식 순 호출
    소멸자는 자식 - 부모 순

태그: ,

카테고리: ,

업데이트:

댓글남기기