오버로딩과 오버라이딩의 차이
이 질문도 내가 면접을 보며 나왔던 문제인데 막연히 알고 있던 내용이라 더 정확히 알고 나중에 다시 공부하기 위해 적어놓는다.
오버로딩과 오버라이딩의 차이에 대해 설명하여 쓰라는 질문이었다.
사실은 플래시와 자바를 쓸 때 인터페이스 구현 때문에 오버라이딩만 계속 써왔고 C++를 할 때는 연산자 오버로딩 외에는 오버로딩에 대해 아는게 없기도 하고 용어가 헷갈려서 정확하게 답변하지 못했었다.
두 문장으로 정리하면
- 오버로딩(Overloading)은 같은 이름의 함수를 매개변수 목록만 다르게 여러 개 정의하는 것. 어느 것을 부를지는 컴파일 시점에 인자 타입을 보고 결정된다(정적 바인딩).
- 오버라이딩(Overriding)은 상속받은 클래스에서 부모의 가상 함수를 다른 내용으로 재정의하는 것. 어느 것을 부를지는 실행 시점에 객체의 실제 타입을 보고 결정된다(동적 바인딩).
struct Base
{
void Print(int); // 오버로딩 대상
void Print(const char*); // 이름은 같고 매개변수만 다르다
virtual void Draw(); // 오버라이딩 대상
};
struct Derived : Base
{
void Draw() override; // 부모의 가상 함수를 재정의
};
여기서 자주 틀리는 두 가지
1. 오버로딩은 클래스 안에서만 하는 것이 아니다.
자유 함수(전역 함수)도 오버로딩된다. 클래스와 무관한 문법이다.
void Log(int);
void Log(double);
void Log(const std::string&);
반환 타입만 다른 것은 오버로딩이 되지 않는다는 점도 같이 기억해 둘 것. 호출부에서 구분할 방법이 없기 때문이다.
2. C++에서 오버라이딩은 virtual이 있어야 성립한다.
virtual이 없는 함수를 자식에서 같은 시그니처로 다시 정의하면 그건 오버라이딩이 아니라 이름 은닉(name hiding) 이다. 실무에서 사고가 자주 나는 지점이다.
struct Base
{
void Draw() { std::cout << "Base\n"; } // virtual이 없다
};
struct Derived : Base
{
void Draw() { std::cout << "Derived\n"; } // 오버라이딩이 아니라 은닉
};
Base* p = new Derived();
p->Draw(); // "Base"가 출력된다. 기대와 다르다.
Base::Draw에 virtual을 붙이면 그제서야 “Derived”가 출력된다.
또 하나, 부모에 오버로딩된 함수가 여러 개 있을 때 자식에서 그중 하나만 다시 정의하면 나머지 오버로딩 전체가 가려진다.
struct Base
{
void Print(int);
void Print(const char*);
};
struct Derived : Base
{
void Print(int); // 이것만 정의했는데
};
Derived d;
d.Print("hello"); // 컴파일 에러. Base::Print(const char*)가 가려졌다.
이때는 using Base::Print;를 자식에 넣어 되살린다.
이런 실수를 막으려면 C++11부터 도입된 override 키워드를 항상 붙이는 것이 좋다. 부모에 대응하는 가상 함수가 없으면 컴파일 에러가 나므로, 오타나 시그니처 불일치를 컴파일러가 잡아준다.
댓글 남기기