본문 바로가기
lang/C,C++

[Class와 OOP] 5. OOP 상속성, (overloading vs overriding)

by Wordbe 2019. 10. 9.
728x90

1. 파생 클래스

상속(Inheritance)

상속은 사용자에게 높은 수준의 코드 재활용성을 제공하며,

클래스 간 계층적 관계를 구성함으로써 다형성 문법의 토대를 마련한다.


클래스 상속

기초 클래스(base class) or 부모 클래스(parent class) or 상위 클래스(super class)

에서 상속을 통해 새롭게 작성되는 클래스는

파생 클래스(derived class) or 자식 클래스(child class) or 하위 클래스(sub class)

라고 합니다.

공통부분은 기초 클래스에서 작성한 것을 그대로 쓰고, 파생 클래스에서 새로운 부분을 추가할 수 있다.

상속 받는 클래스가 하나이면 단일 상속(single inheritance),

여러개면 다중 상속(multiple inheritance)라고 합니다.


파생 클래스 특징

반드시 자신만의 생성자를 작성해야 합니다.


파생 클래스의 객체 생성 순서

1) 파생 클래스의 객체를 생성하면, 제일 먼저 기초 클래스의 생성자가 호출됩니다.

​ 이 때, 기초 클래스 생성자는 상속받은 멤버 변수의 초기화를 진행합니다.

2) 파생 클래스의 생성자가 호출됩니다.

3) 파생 클래스의 수명이 다하면 소멸자 호출되고, 그 후 기초 클래스의 소멸자가 호출됩니다.

img

2. 멤버 함수 오버라이딩(overriding)

함수 오버로딩 vs 함수 오버라이딩

앞서 배운 함수 오버로딩(overloadling)은 서로 다른 시그니처를 갖는 여러 함수를 같은 이름으로 정의하는 것이었습니다.

잠시 함수 오버라이딩에 다시 설명해보겠습니다.

함수 오버로딩(function overloading)은 매개변수를 달리하여 같은 이름의 함수를 중복하여 정의하는 것을 의미합니다.

이는 객체 지향 프로그램의 특징 중 다형성(polymorphism)의 구현을 돕습니다.

함수시그니처(function signature)란 함수의 원형에 명시되는 매개변수 리스트입니다.

두 함수의 만약 매개변수의 개수매개변수 타입이 모두같으면, 시그니처가 같다고 할 수 있습니다.

즉, 함수 오버로딩은 서로 다른 시그니처를 갖는 여러 함수를 같은 이름으로 정의하는 것입니다.

C++ 컴파일러는 사용자가 오버로딩된 함수를 호출하면, 그것과 같은 시그니처를 가지는 함수의 원형을 찾아 호출해 줍니다.


함수 오버라이딩(overriding)이란 이미 정의된 함수를 무시하고, 같은 이름의 함수를 새롭게 정의하는 것입니다.

파생클래스는 상속받을 때 명시한 접근 제어 권한에 맞는 기초 클래스의 모든 멤버를 상속받습니다.

이렇게 상속받은 멤버함수는 그대로 사용해도 되고, 필요한 동작을 위해 재정의하여 사용할 수도 있습니다.

오버라이딩은 멤버 함수의 동작을 재정의하는 것으로, 함수의 원형은 기초 클래스의 기존 멤버 함수의 원형과 같아야 합니다.

방법은 두가지 입니다.

1) 파생 클래스에서 직접 오버라이딩

2) 가상 함수를 이용해 오버라이딩

class Person{
private:
    string name_;
    int age_;
public:
    Person(const string& name, int age);
    void showPersonInfo();
};

class Student: public Person{
private:
    int student_id_;
public:
    Student(int sid, const string& name, int age);
    void showPersonInfo();
};

Person::Person(const string& name, int age){
    name_ = name;
    age_ = age;
}
void Person::showPersonInfo(){
    cout << "name(" << name_ << ") " << "age(" << age_ << ")" << endl;
}
Student::Student(int sid, const string& name, int age): Person(name, age){
    student_id_ = sid;
}
void Student::showPersonInfo(){
    cout << "ID(" << student_id_ << ")" << endl;
}


// main
Person kim("JSkim", 26);
kim.showPersonInfo();
Student lee(2017250043, "SPlee", 23);
lee.showPersonInfo();
lee.Person::showPersonInfo();

Student 클래스에서 오버라이딩한 showPersonInfo() 함수는 이와 이름이 같은 부모 클래스의 showPersonInfo()를 덮어씌우고(무시하고) 새롭게 정의한 함수가 실행됩니다.

기존, 부모 클래스의 멤버 함수를 호출하고 싶다면, 위 코드 맨 밑줄과 같이 범위 지정 연산자(::)를 사용하여 파생클래스에서도 기초 클래스의 원래 멤버함수를 호출할 수 있습니다.


파생클래스에서 오버라이딩 문제점

일반적인 상황에서는 잘 동작하나,

포인터 변수를 사용할 때 예상치 못한 결과를 반환할 수 있습니다.

Person* ptr_person;
Person kim("JSkim", 26);
Student lee(2017250043, "SPlee", 23);

ptr_person = &kim;
ptr_person->showPersonInfo();
ptr_person = &lee;
ptr_person->showPersonInfo();

위 결과는 둘다 부모 클래스의 showPersonIfon()를 호출합니다.

C++ 컴파일러는 포인터 변수가 실제로 가리키는 객체의 타입을 기준으로 함수를 호출하지 않고,

해당 포인터 타입(Person*)을 기준으로 함수를 호출하기 때문입니다.

근데 애시당초 Student* ptr_student 를 만들어야 하는 것 아닌가 생각이 들긴합니다.

아무튼, C++에서는 이러한 문제를 해결하기 위해 virtual 키워드를 사용한 가상 함수를 제공합니다.

class Student : public Person{
private:
    int student_id_;
public:
    Student(int sid, const string& name, int age); // Constructor
    virtual void ShowPersonInfo(); // 파생 클래스에서 상속받은 멤버 함수의 재정의

};

이렇게 멤버 함수를 가상함수로 선언하면, 포인터가 실제로 가리키는 객체에 따라 호출하는 대상을 바꿀 수 있게 됩니다.

3. 다중 상속(Multiple Inheritance)

두 개 이상의 클래스로부터 멤버 상속을 받아 파생 클래스를 생성하는 것을 말합니다.

쉼표(,)를 이용하여 여러개의 기초 클래스를 명시합니다.


다중상속 문제점

1) 상속받은 여러 기초 클래스에 같은 이름의 멤버가 존재할 수 있습니다.

2) 하나의 클래스를 간접적으로 두 번이상 상속받을 가능성이 있습니다.

3) 가상 클래스가 아닌 기초 클래스를 다중 상속하면, 기초 클래스 타입의 포인터로 파생 클래스를 가리킬 수 없습니다.

다중상속은 프로그래밍을 복잡하게 만드는 반면, 실용성은 그다지 높지 않습니다.

될 수 있으면 사용을 자제하는 것이 좋습니다.

[출처 - TOPSCHOOL.com http://tcpschool.com/cpp]

728x90

댓글