객체지향프로그래밍(17) - 오버라이딩

오버라이딩의 정의와 활용

Featured image

🔚 짧게 하는 복습

✅ 1. 집합 관계의 정의와 특징을 안다.

✅ 2. 합성 관계의 정의와 특징을 안다.

✅ 3. 지금까지 배운 클래스간 구조의 장단점들을 안다.

혹시 기억이 안 난다면, 다시 돌아가자


이번 강의부터는 상속된 클래스를 통해 유연하게 프로그래밍을 하는 방법을 배운다.

아직 정확하게 정의를 할 수는 없지만 이를 다형성이라는 말을 쓴다.

객체지향 프로그래밍의 꽃, 다형성에 대해서 이번 강의부터 알아보자.


오버라이딩이란?

일단은 그 전에 알아야 할 지식이 있다.

C++에서는 서로 다른 클래스가 같은 이름과 매개변수를 가진 메소드를 가질 수 있다고 했다.

그리고 그 두 개의 메소드가 설령 static 메소드라서 객체를 통하지 않아도, 범위 지정자 ::를 통해서 구분할 수 있다고 했다.

그런데 그 두 개의 클래스가 상속 관계인 상위, 하위 클래스 내에서 똑같은 이름의 메소드를 가질 수 있을까?

정답을 미리 말하자면, 가능하다

이렇게 상위 클래스에서 정의된 메소드를 상속된 하위 클래스가 똑같은 이름과 매개변수의 조합으로 재정의하는 것오버라이딩이라고 한다.

아래의 코드를 보자.

상위 클래스에서 bark를 정의해놓고, 하위 클래스에서 한 번 더 bark를 재정의하고 있다.

이럴 때 어떻게 메소드가 호출될까?

사실 전혀 특이한 것이 없다. 이미 배웠기 때문이다.

‘자신의 클래스부터 아래에서 위로’이 원칙만 알고 있다면 절대 헷갈리지 않는다.

우선 ani의 bark를 보자. ani에서 bark를 하면 시작이 animal 클래스이다.

이미 자신의 클래스 내에 bark가 정의되어 있다. 굳이 위로 올라갈 이유가 없다.

그냥 animal::bark가 실행된다.

반면 badukee는 어떤가? badukee의 시작은 dog 클래스이다.

badukee 입장에서도 자신의 클래스 내에 bark가 정의되어 있다. 역시 굳이 올라갈 이유가 없다.

그냥 dog::bark가 실행되면 되는 것이다.


오버로딩과 오버라이딩의 차이

사람들이 자주 혼동하는 개념이라서, 한번 짚고 넘어가면 좋을 듯하다.

오버로딩이란 매개변수의 개수나 타입에 따라 함수가 재정의되는 것을 의미한다.

반환 타입이나 범위 지정자가 다른 것은 오버로딩이 아니다.

아래와 같은 예가 오버로딩의 대표적인 예이다.

int add(int a, int b) {return a+b;}
double add(double a, double b) {return a+b;}

즉 아래와 같은 것은 오버로딩이 아니다.

double add(double a, double b) {return a+b;}
//double add(double a, int b) {return a+b;} 얘는 아님
class dog{
    public:
    void bark() {
        std::cout<<"Bow wow"<<std::endl;
    }
}

class cat{
    public:
    void bark() {
        std::cout<<"Meow Meow"<<std::endl;
    }
}

// 두 bark는 오버로딩 관계가 아니다.

오버라이딩상속 관계에서만 유의미하며, 사실 함수의 매개변수뿐만 아니라 반환 타입, const/static의 유무 등(이를 시그니처라고 한다.)가 정확히 같아야한다.

만약 상속 관계를 통했고 함수의 이름이 같더라도, 매개변수의 타입이나 개수가 다르다면 그것은 오버로딩 관계가 된다.

#include <iostream>

class Animal {
public:
    void sound() {
        std::cout << "Animal makes a sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void sound() {
        std::cout << "Dog barks" << std::endl;
    } // 얘는 오버라이딩

    void sound(int volume) {
        std::cout << "Dog barks loudly" << std::endl;
    } // 얘는 오버로딩
};

오버라이딩의 장단점

오버라이딩은 상위 클래스를 건들지 않고 유연하게 하위 클래스를 확장할 수 있다는 점에서 상속 계층의 유연한 확장을 보장한다.

그리고 후에 배우겠지만, 하나의 코드가 여러 역할을 할 수 있다는 다형성을 가지게 된다.

반면에 득이 있으면 실도 있는 법, 오버라이딩이 많아지면 코드의 가독성이 떨어지고 의도치 않은 버그가 일어날 수 있다.


📖 오늘의 핵심(다 알기 전까지는 넘어가지 말자❗)

✅ 1. 오버라이딩의 정의를 안다.

✅ 2. 오버로딩과 오버라이딩의 차이를 안다.

✅ 3. 오버라이딩의 장단점을 안다.

⚠️ 오버로딩은 매개변수의 차이가 있을 때, 오버라이딩은 상속 간 시그니처가 같을 때 재정의를 의미한다.

💣 과제, 아래 코드의 결과를 예측해보자.

#include <iostream>

class A{
    public :
    void foo() {std::cout<<"A"<<std::endl;}
};


class B : public A{
    public :
    void foo() {std::cout<<"B"<<std::endl;}
};


class C : public A{
};


class D : public B{
    public :
    void foo() {A::foo();}
};

class E : public B{
};

class F : public C{
    public :
    void foo() {std::cout<<"F"<<std::endl;}
};

class G : public C{
};

int main(){
    A a;
    B b;
    C c;
    D d;
    E e;
    F f;
    G g;

    a.foo();
    b.foo();
    c.foo();
    d.foo();
    e.foo();
    f.foo();
    g.foo();

}