[C++] Ch06. OOP 3
Prog. Langs & Tools/C++

[C++] Ch06. OOP 3

 

이번 포스팅에서는 OOP에 대한 내용을 마지막으로 정리해 보고자 한다.

상속

상속이란 다른 클래스의 특성을 내려 받는 것을 의미한다. 부모 클래스와 자식 클래스로 이루어지며 자식 클래스는 부모 클래스의 멤버변수와 멤버 메서드, 소멸자를 가진다. 또한 자식 클래스는 멤버 변수 및 메서드를 추가할 수 있다.

// Animal.h
class Animal
{
public:
  Animal(int age);
private:
  int mAge;
};

// Cat.h
class Cat : public Animal
{
public:
  Cat(int age, const char* name);
private:
  char* mName;
};

// Cat.cpp
Cat::Cat(int age, const char* name)
  : Animal(age)
{
  size_t size = strlen(name) + 1;
  mName = new char[size];
  strcpy(mName, name);
}

상속 시에는 상속 접근 종류와 클래스 멤버 접근 종류 중 더 제한적인 접근 제어자를 선택한다. 예를 들면 상속 접근 종류가 protected인데 클래스 멤버 접근 종류가 public이면 상속은 protected로 이루어진다.

생성자의 호출 순서는 다음과 같이 이루어 진다. 부모 클래스의 생성자가 명시적으로든 암시적으로든 먼저 호출이 된다. 그리고 나서 자식 클래스의 생성자가 호출이 된다. 만약 부모 클래스의 특정 생성자를 호출하기 위해서는 초기화 리스트를 사용해야 한다.

Cat::Cat(int legs, int age, const string& callingName)
  : Animal(legs, age) // 명시적 호출
  , mCallingName(callingName)
{
}

만약 매개변수가 없는 생성자가 부모클래스에 있다면 자식 클래스에서 생성자를 따로 적어주지 않아도 암시적으로 호출이 된다. 하지만 매개변수가 있는 생성자가 부모클래스에 있다면 자식 클래스에서는 부모클래스의 생성자를 암시적으로 호출해줄 수 없으므로 컴파일 에러가 난다.

// Animal.cpp
Animal::Animal(int age)
  : mAge(age)
{
}

// Cat.cpp
Cat::Cat(int age, const string& name)
// 암시적으로 Animal()을 호출, 컴파일 에러
{
  size_t size = strLen(name) + 1;
  mName = new char[size];
  strcpy(mName, name);
}

// main.cpp
Cat* myCat = new Cat(2, "Mew");

소멸자는 순서가 반대이다. 자식 클래스 먼저 지우고, 부모 클래스를 지운다. 자식 클래스 소멸자 마지막에 부모 클래스 소멸자가 자동적으로 호출된다.

// Animal.cpp
Animal::~Animal()
{
}

// Cat.cpp
Cat::~Cat()
{
  delete mName;
  // ~Animal()을 호출
}

// main.cpp
delete myNeighboursCat;

 

다형성

다형성은 같은 무늬를 가진 객체가 실제로 실행해 보니 다르게 동작하는 현상을 말한다. 다형성에 대해 알기 전에 멤버함수에 대해서 이해할 필요가 있다. 멤버 함수는 메모리 어딘가에 위치해 있다. 이러한 멤버 함수는 컴파일 시에 딱 한 번만 메모리에 할당이 된다. 저수준에서는 멤버함수와 전역함수가 크게 다르지 않다. 코드 레벨에서 확인해 보면 두 클래스(부모클래스가 같은 두 자식 클래스) 안의 멤버함수는 같은 메모리에 들어있으며 나 자신의 포인터로 작동함을 알 수 있다.

출처 : 포큐 아카데미

C++의 상속은 자바와 약간 다른데 아래의 코드를 보면 새로운 인스턴스 생성 시 포인터를 따라서 메서드가 실행이 됨을 알 수 있다.

class Animal
{
public:
  void Speak(); // "An animal is speaking"
}
class Cat : public Animal
{
public:
  void Speak(); // "Meow"
}

Cat* myCat = new Cat();
myCat->Speak(); // Meow
Animal* yourCat = new Cat();
yourCat->Speak(); // An animal is speaking

이러한 현상이 일어나는 이유는 C++에서는 정적 바인딩을 하기 때문이다. 자바가 실체를 따라가는 반면 C++는 무늬를 따라간다. C++에서 실체를 따라가도록 하기 위해서는 가상 함수를 사용해야 한다. 참고로 자바의 경우 모든 것이 기본적으로 가상함수이다. 가상함수는 실체의 멤버함수가 호출되는 것으로, 동적 바인딩이라고 하기도 한다. 정적 바인딩이 컴파일 타임에서 이루어진다면, 동적 바인딩은 런타임에서 일어난다.

virtual 키워드를 생략하면 메모리 누수(memory leak)가 일어날 확률이 높다. 따라서 가상 소멸자를 만들어 주어야 한다.

// Animal.h
class Animal
{
public:
  virtual ~Animal();
private:
  int mAge;
};

// Cat.h
class Cat : public Animal
{
public:
  // 여기는 virtual 키워드 생략 가능
  virtual ~Cat();
private:
  char* mName;
};

// Cat.cpp
Cat::~Cat()
{
  delete mName;
}

 

Q. 다형성에 대해서 설명해보자. 간단하게 4가지 단어로 설명할 수 있다. 

 

다중 상속

다중 상속은 두 개 이상의 부모 클래스로부터 상속받은 클래스를 말한다. 어떤 부모의 생성자가 먼저 호출될까? 정답은 상속받은 순서대로 호출이 된다. C++은 자바처럼 super를 쓸 수가 없는데, 그 이유는 다중 상속이 가능하기 때문이다.

만약 부모 클래스들에 같은 이름의 함수들이 있고 그 함수가 호출된다면 어떤 클래스의 함수를 호출할까? 이 경우는 해당 부모 클래스 명을 적어서 어떤 클래스에서 상속을 받을지 명시해 주어야 한다.

// Faculty.h
class Faculty
{
public:
  void DisplayData();
};

// Student.h
class Student
{
public:
  void DisplayData();
};

// TA.h
class TA : public Student, public Faculty
{
}

// Main.cpp
TA* myTA = new TA();
// myTA->DisplayData(); // 어디서 상속받지?
myTA->Student::DisplayData();

만약 어떤 부모 클래스에서 두 개의 자식 클래스를 가지고, 그 두 개의 자식 클래스를 부모 클래스로 상속받는 자식 클래스가 있다면 어떻게 될까? 이 경우는 가상 부모 클래스를 지정해 주어야 한다. Animal -> Tiger, Lion -> Liger 라는 클래스 구성이 있다고 가정해보자.

// Animal.h
class Animal
{
};

// Tiger.h
class Tiger : virtual public Animal
{
};

// Lion.h
class Lion : virtual public Animal
{
};

// Liger.h
class Liger : public Tiger, public Lion
{
};

다중 상속은 현재는 쓰는 것을 권장하지 않는다. 지금은 오히려 인터페이스를 쓸 것을 권장한다. 마치 자바처럼 말이다.

인터페이스를 알기 위해서는 먼저 추상클래스에 대해서 알아야 한다. 추상클래스는 순수 가상함수를 가지고 있는 부모 클래스를 말한다. 추상클래스에서는 개체를 만들 수 없고, 포인터나 참조형으로 추상클래스를 사용할 수 있다.

인터페이스는 추상 클래스와 거의 비슷한 개념이다. 차이가 있다면 인터페이스는 함수만 가지고 있다. 인터페이스는 데이터를 가지고 있지 않다. 인터페이스를 통해서 동작을 다중 상속 받을 수가 있다. C++에서는 자체적으로 인터페이스를 지원하지 않는다. 따라서 순수 추상 클래스를 사용하여 인터페이스를 만들어야 한다. 인터페이스는 순수 가상함수만 가지며 멤버 변수는 없다.

class IFlyable
{
public:
  virtual void Fly() = 0;
};

 

이렇게 세 번에 나누어서 개체지향 프로그래밍에 대한 내용을 정리해 보았다. 중요한 내용이기 때문에 여러번 복습해야겠다!

'Prog. Langs & Tools > C++' 카테고리의 다른 글

[C++] Ch08. 인라인 함수, static 키워드  (0) 2020.11.30
[C++] Ch07. 캐스팅(Casting)  (0) 2020.11.16
[C++] Ch06. OOP 3  (0) 2020.10.19
[C++] Ch05. OOP 2  (0) 2020.10.01
[C++] Ch04. OOP 1  (0) 2020.09.22
[C++] Ch03. 파일 입출력(I/O)  (0) 2020.08.26