본문 바로가기
프로그래밍/C || CPP

부모와 자식클래스 가상 함수의 반환타입을 다르게 재정의 할 수 있을까? (Overriding, Return Value)

by _BlankSpace 2019. 6. 27.

 

가상 함수 코드를 보다가, 우연히 부모클래스의 가상 함수와 자식클래스의 가상 함수의 리턴 타입이 다르다는 것을 본 적이 있습니다.

혹시, 부모 클래스에서 상속 받은 가상 함수의 리턴 값이 달라도 상관 없다는 것을 아시나요?

 

물론, 이 포스팅을 검색해서 들어오셨다면, 그 사실을 모르셨거나, 정확한 이유가 궁금해서겠죠.

먼저, 답부터 말하면 가능합니다.

 

그런데, 일반적으로 오버라이딩을 공부하신 분이라면 반환 타입이 같아야 한다는 것은 모두 아는 사실인데, 그럼 불가능한 것이 아닌가라고 생각하실 수 있습니다.

 

그래서 열심히 구글링해본 결과, 부모 클래스의 함수 타입에 대해서 상속받은 클래스의 함수가 대체가능하다면 반환타입이 달라도 된다고 합니다.

말로만 설명하면 이해하기 어려우므로, 바로 간단한 예제로 설명하도록 하겠습니다.

 

 

부모 클래스와 자식 클래스의 가상 함수 반환타입이 같은 경우.

아래 예제는 자기 자신의 객체를 만들어서 반환하는 clone() 함수를 만드는 예제입니다.

 

#include <iostream>
class SuperClass {
public:
    virtual SuperClass* clone() const {
        return new SuperClass(*this);
    }
};

class SubClass : public SuperClass{
public:
    SuperClass* clone() const final {
        return new SubClass(*this);
    }
};

int main(void) {
    SubClass *sub1 = new SubClass();
    SuperClass *super = sub1->clone();
    //SubClass *sub2 = super; // 에러 발생.
    SubClass *sub2 = dynamic_cast<SubClass*>(super);
    return 0;
}

 

설명

먼저, SuperClass에서 자기 자신을 생성하는 clone() 함수를 가상 함수로 만들었습니다.

 

class SuperClass {
public:
    virtual SuperClass* clone() const {
        return new SuperClass(*this);
    }
};

 

이후, SuperClass의 자식클래스인 SubClass에서는 자기 자신을 생성하는 clone() 함수를 오버라이딩 합니다. 이때, 가상 함수 clone()의 반환 타입은 SuperClass인 것을 확인할 수 있습니다.

따라서, 자식클래스인 SubClass의 객체를 생성하지만, 리턴할 때는 SuperClass의 객체로 변환되는 것입니다.

 

class SubClass : public SuperClass{
public:
    SuperClass* clone() const final {
        return new SubClass(*this);
    }
};

 

이것은 아래와 같은 문제가 발생합니다.

아래는 main 함수에서 SubClass의 객체로 sub1을 생성합니다. 이후에, sub1 객체를 clone() 함수를 이용하여 복사하려 하지만, 바로 SubClass 객체에 대입할 수 없습니다.

그 이유는 SubClass clone() 함수의 반환 타입이 SuperClass이기 때문입니다. 이로 인해서, SuperClass 객체를 생성하여 대입해야 하는 문제가 생긴 것입니다.

 

int main(void) {
    SubClass *sub1 = new SubClass();
    SuperClass *super = sub1->clone();
    //SubClass *sub2 = super; // 에러 발생.
    SubClass *sub2 = dynamic_cast<SubClass*>(super);
    return 0;
}

 

때문에, 결국, super라는 객체는 sub2라는 객체에 대입되기 위해서 dynamic_cast로 캐스팅을 하여 문제를 해결하게 됩니다.

이는 결국 코드가 복잡해지는 문제가 발생합니다.

 

부모와 자식의 가상 함수 반환타입이 다르다면, 이러한 문제를 해결할 수 있을까요? 잠시 생각해보시고, 바로 아래로 따라와주세요.

 

 

부모 클래스와 자식 클래스의 가상 함수 반환타입이 다른 경우.

아래 예제는 위에 예제와 아주 유사하지만, 가상 함수의 반환타입이 다른 경우입니다.

일단, 유의해야할 부분은 아주 예외적인 상황으로 가상 함수의 반환타입이 다를 수 있다는 것입니다.

 

#include <iostream>
class SuperClass {
public:
    virtual SuperClass* clone() const {
        return new SuperClass(*this);
    }
};

class SubClass : public SuperClass{
public:
    SubClass* clone() const final {
        return new SubClass(*this);
    }
};

int main(void) {
    SubClass *sub1 = new SubClass();
    SubClass *sub2 = sub1->clone();
    return 0;
}

 

설명

SuperClass 부분은 위와 같으므로 설명은 생략하도록 하겠습니다.

 

SubClass의 clone() 반환타입이 위의 예제와 달라졌다는 것을 느끼실 수 있으신가요? 이번 포스팅에서 주구장창 이야기했던, 반환타입을 SubClass로 입력했다는 점이 달라졌습니다.

이로 인해서, SubClass의 객체는 자기 자신을 바로 반환할 수 있게 되겠죠. 그럼, main에서 사용하는 방법은 어떻게 달라졌는 지 확인해볼까요.

 

class SubClass : public SuperClass{
public:
    SubClass* clone() const final {
        return new SubClass(*this);
    }
};

 

위에서는 SubClass의 객체를 생성한 후, 복사하려면 바로 대입할 수 없었습니다. 왜냐하면, clone() 반환타입이 달랐으니 말입니다.

하지만, 반환타입이 SubClass로 바뀌면서, 아래 예제 *sub2 = sub1->clone() 처럼 간결하게 코드를 만들 수 있게됩니다.

즉, dynamic_cast와 같은 불필요한 캐스팅을 줄일 수 있게 되는 것입니다.

 

int main(void) {
    SubClass *sub1 = new SubClass();
    SubClass *sub2 = sub1->clone();
    return 0;
}

 

이상으로 포스팅 내용 정리를 마치겠습니다. 혹시라도 잘못된 내용이 있다면 댓글 남겨주세요.

댓글