C++ - OOP 3

mohadang·2022년 9월 24일
0

C++

목록 보기
7/48
post-thumbnail

상속

  • 다른 개발자들은 상속을 많이 사용 할까?

public 상속

  • C++ 에서는 상속을 할 때는 대부분 public 상속을 함
  • 사실 private나 protected 상속은 거의 사용 안한다고 봐도 무방
// [Animal.h]
class Animal
{
public :
  Animal(int age);
  virtual ~Animal();

  virtual void Move() const;
  virtual void Speak() const;

  int GetAge() const;

private:
  int mAge;
};

// [Cat.h]
class Cat : public Animal
{
public:
  Cat(int age);

  virtual void Move() const;
  virtual void Speak() const;
};

// [Cat.cpp]
Cat::Cat(int age)
  // OOP의 중요한 개념중 하나는 부모 클래스에서 정상적으로 동작 할 것이라고 기대하며
  // 데이터만 넘겨주면 모든게 준비되어야 한다.
  : Animal(age)  
{
}

부모 클래스의 생성자 호출

  • Java는 다음과 같이 호출 가능 하다
public Cat(int age, string name)
{
  super(age);// 부모 클래스의 이름을 알 필요가 없어서 간결...
}
  • C++은 왜 부모 클래스의 이름을 알아서 Animal(age)와 같이 호출해 주어야 할 까??
    • 다중 상속!!!! , 무었을 호출해야 할지 지정해야 하기 때문...
  • C++에서는 부모 클래스의 생성자를 반드시 하나는 호출해서 생성해야 한다.
    • 그래서 부모 클래스가 기본 생성자가 없다면 문제가 발생할 가능 성이 있다.
  • 부모의 특정 생성자를 호출하기 위해서는 초기화 리스트 이용해야 함.
  • 생성자 호출 순서
    • 부모 생성자 부터 먼저 호출되고 자식 생성자가 호출됨

복사 생성자의 상속

  • 부모 클래스 복사 생성자 구현 O, 자식 클래스 복사 생성자 구현 X
    • 부모 클래스 복사 생성자 호출
  • 부모 클래스 복사 생성자 구현 O, 자식 클래스 복사 생성자 구현 O
    • 자식 클래스 복사 생성자 호출

암시적인 생성자 호출

[Animal.h]
Animal::Animal()
  :mAge(0)
{}

// [Cat.h]
// 암시적으로 부모의 기본 생성자(Animal()) 호출
Cat::Cat()
{}
[Animal.h]
Animal::Animal(int age)
  : mAge(age)
{}

[Cat.h]
// 암시적으로 부모의 기본 생성자(Animal()) 호출
// 그러나 컴파일 에러... 부모의 기본 생성자 없어졌기 때문에
Cat::Cat(int age, const string& name)
{}

소멸자의 호출 순서

[Animal.h]
Animal::~Animal()
{}

//[Cat.h]
// 암시적으로 부모의 기본 생성자(Animal()) 호출
Cat::~Cat()
{
  delete mName;
  // 여기서 암시적으로 ~Animal() 호출
}
  • 생성자의 호출 순서 : 부모 -> 자식
  • 소멸자의 호출 순서 : 자식 -> 부모

다형성 (Polymorphism)

  • Poly : 여러가지
  • Morph : 변한다
  • 멤버 함수는 메모리 어딘가에 있는가??
Cat* myCat = new Cat(5, "coco");
Cat* yourCat = new Cat(5, "Mocha");
myCat->GetName();
yourCat->GetName();
  • 멤버 함수도 메모리 어딘가에 존재
    • 모든것이 메모리 어딘가에 위치
    • .txt, code 섹션이라는 곳에 위치
  • 그런데 각 개체마다 멤버 함수들이 메모리에 잡혀 있나??
myCat->GetName();
yourCat->GetName();
/*
둘의 동작은 완전히 일치 하는데 메모리를 따로 잡을 필요가 있을까??
위 두 함수의 유일한 차이점은 호출해 주는 개체의 메모리이다.
그래서 개체마다 멤버 함수들은 메모리에 따로 잡혀 있는것이 아니라 메모리 한 곳에 있다.
(마치 중복 코드는 나쁘기에 피하라는 원리와 비슷...)
*/
  • 멤버함수는 '컴파일' 시 '딱 한번만 메모리에 할당됨'.

    • 저수준에서는 멤버 함수와 전역 함수가 별 차이가 없다.
  • 멤버 함수를 호출 할 때 일어나는 일.

    • 멤버 함수를 호출 할 때 사실 첫번째 인자로 개체 자기 자신이 암시적으로 들어간다
    // GetName 멤버 함수는 인자가 전혀 없지만
    // 실제 컴파일 단에서 이렇게 변환된다.
    // 어셈블리로 보면 멤버 함수 호출시 ecx 레지스터에 개체의 메모리 넘김
    char* Cat::GetName(Cat* this)
    {
      return this->Name;
    }
  • "멤버 함수는 개체의 소유가 아니라 클래스의 소유라고 생각하면 된다."

  • 정리하자면, 개체의 멤버 함수를 호출할 때는 같은 메모리에 위치한 멤버 함수를 호출하고 차이점은
    첫번째 인자로 넘겨지는 개체의 주소값이다

  • 정적 바인딩

    • 정적 바인딩은 개체의 멤버 함수가 정적 타입에 있는 함수로 바인딩 되는 것이다
    • C++에서는 기본이 정적 바인딩임
class Animal
{
public:
  void Speak();
};

class Cat : public Animal
{
public:
  void Speak();
};

// "Cat : 정적 타입"
// "Cat : 동적 타입"
Cat* myCat = new Cat();
myCat->Speak();// Cat 의 Speak 함수 호출


// "Animal : 정적 타입"
// "Cat : 동적 타입"
Animal* myAnimal = new Cat();
myAnimal->Speak();// Aniaml의 Speak 함수 호출
  • 동적 바인딩
    • 가상(virtual) 함수
    • 다향성의 핵심
class Animal
{
public:
  virtual void Speak();
};

class Cat : public Animal
{
public:
  void Speak();
};

// Cat의 Speak()함수 호출
// 동적 타입에 바인딩 되기에..
Animal* myAnimal = new Cat();
  
// 가상 함수는 언제나 자식 클래스의 멤버 함수가 호출 됨
// 동적 바인딩을 늦은 바인딩이라고도 함,
// 이유는 실행중에 호출될 함수가 결정 되니까...
  • 가상 테이블
    • class에 virtual 함수 쓰는 순간 동적 바인딩을 위해 가상 테이블이 생성됨
    • 가상 테이블에는 호출 되어야할 가상 멤버 함수들의 리스트가 명시되어 있음.
    • 클래스 마다 가상 테이블 존재... Cat과 Dog의 가상 테이블안에 함수 주소들은 다를 것이다.
    • 개체 생성할때 마다 가상 테이블 주소도 같이 가지고 있다
    • 가상 테이블은 look-up 테이블 이라고도 불림
    Q : Speak()함수 어디 있니 ???
    A : 어디보자... 가상 테이블이 0xAAAAAAAA에 있고 두번째 함수니까
        0xAAAAAAAE 에 존재하겠네...  
    • 가상 테이블은 클래스에 virtual 멤버 함수가 하나라도 있어야만 생성 된다.
    • virtual 키워드가 없다는 것은 그 클래스는 모든 멤버 함수가 정적 바인딩을 하기 때문에 가상 테이블이 필요 없다는 뜻이다.
  • 소멸자도 멤버 함수다. 즉 정적바인딩이나 동적 바인딩이 된다는 것이다.
class Animal
{
public:
  ~Animal()
  {
  }
}

class Cat : public Aniaml
{
public:
  ~Cat()
  {
    delete Name;
  }
}
  • 소멸자가 정적 바인딩 되면 문제 발생
// Cat 클래스의 소멸자도 정적바인딩 되어 있다. 그렇다는 뜻은...
Animal* animal = new Cat("coco");
// ~Cat() 소멸자가 아닌 ~Animal()의 소멸자가 호출되어 Memory Leak이 발생
delete animal;
  • 해결책은 virtual ~Animal()로 가상 소멸자를 만들어 주면 됨

    • virtual ~Cat()도 넣어 주면 좋지만 사실 부모 소멸자가 가상이면 자식 소멸자도 자동으로 가상이 됨
    • 하지만 명확하게 표현하기 위해 자식 소멸자인 ~Cat에서 앞에 virtual 붙이는게 좋음
    • "모든 클래스의 소멸자는 가상으로 만들것"
      • 상속을 안 해도??, 동적 메모리를 할당 안해도 ??,
        • YES. 누군가 내 클래스를 언제 어떻게 사용할지 모르기 떄문에... 방어적으로 virtual 을 붙이는 것이 좋은 습관임.
      • 부모가 이미 가상 소멸자로 만들었는데도???
        • YES. 알아보기 쉽다는 점에서 넣는 것이 좋다.
    • 모든 소멸자는 가상 소멸자로...
    • 가상함수와 다형성은 따라가는 개념이다
  • 다중 상속

    • 가능하면 피하고 싶은...
    • Java나 C#에서는 지원을 하지 않음.
    • C++의 잊혀져 가는 흑마법

추상 클래스

  • 인스턴스로 만들 수 없는 클래스
  • 순수 가상 함수가 하나라도 있으면 추상 클래스
class Animal
{
  public void Speak() = 0; // 순수 가상 함수
};
  • 순수 가상 함수

    • 상속받는 클래스에서 반드시 구현 해야함.
    • 추상 클래스를 받는 순간 순수 가상 함수를 구현해야 한다 안 그러면 개체를 생성 할 수 없음
    • 추상 클래스를 받는 그 클래스는 vtable에 순수 가상 함수 주소를 위한 빈 공간이 생기고 사용자는 그 빈 공간을 순수 가상 함수를 구현 함으로서 채워야 한다.
  • 인터페이스

    • Java, C#에서는 interface 를 따로 지원함.
    • C++에서는 따로 지원하지 않음.
    class IFlyAble
    {
    public:
      virtual void Fly() = 0;
    }
    • 따라서 다음과 같이 임의의 규칙을 정의해 놓고 인터페이스를 흉내내는 "순수 추상 클래스"를 만들어야함.
        1. 추상 클래스로 만든다.
        1. 순수 가상 함수만 있어야 한다.
        1. 멤버 변수들은 없어야 한다.
        1. 클래스 이름은 I로 시작해야 한다.
profile
mohadang

0개의 댓글