HOWTO · C++

C++에서 함수 오버로딩 VS 오버라이드

이 기사에서는 C++에서 함수 오버로딩 VS 재정의를 사용하는 방법을 보여줍니다.

이 페이지의 내용

함수 오버로딩과 함수 오버라이딩은 C++에서 서로 다른 문제를 해결합니다. 함수 오버로딩은 같은 범위의 함수들이 이름은 같고 매개변수 목록은 다르게 정의하는 것으로, 컴파일러가 호출 인수를 바탕으로 적절한 함수를 선택합니다. 함수 오버라이딩은 파생 클래스가 상속받은 virtual 함수를 일치하는 구현으로 대체하는 것으로, 기본 클래스 참조나 포인터를 통한 호출은 런타임에 파생 객체의 함수로 디스패치됩니다.

C++에서 오버로딩과 오버라이딩의 의미

오버로딩은 매개변수가 서로 다른 함수 중에서 적절한 함수를 선택하는 것입니다. 오버라이딩은 상속 계층에서 동작을 대체하는 것입니다. 함수의 반환형만으로는 오버로드를 구분할 수 없으며, 오버라이딩하려면 기본 클래스에 virtual 함수가 있어야 합니다. override 지정자를 사용하면 파생 클래스의 선언이 기본 클래스의 가상 함수와 실제로 일치하는지 컴파일러가 검사합니다.

함수 오버로딩의 동작 방식

한 범위 안에 같은 이름의 함수를 선언하되, 각 함수의 매개변수 목록은 서로 다르게 작성합니다. 매개변수 목록은 매개변수의 개수나 형식이 다를 수 있습니다. 호출 시 오버로드 해결은 인수와 후보 함수의 매개변수 형식을 바탕으로 이루어집니다. 반환형은 오버로드를 선택하는 데 사용되지 않으므로 반환형만 다른 두 선언은 유효한 오버로드 집합이 아닙니다.

다음 예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#include <iostream>

class Formatter {
public:
    void show(int value) const {
        std::cout << "integer: " << value << '\n';
    }

    void show(double value) const {
        std::cout << "decimal: " << value << '\n';
    }
};

int main() {
    Formatter formatter;
    formatter.show(7);
    formatter.show(2.5);
}
integer: 7
decimal: 2.5

제공된 런타임 근거에 따르면 이 예제는 해당 출력을 내며 성공적으로 실행되었습니다. 정수 인수와 소수 인수는 서로 다른 show 오버로드를 선택합니다. 오버로드를 추가할 때는 매개변수 목록이 의미 있게 달라야 하며, 호출이 모호해지지 않는지 확인하세요.

함수 오버라이딩의 동작 방식

기본 클래스는 함수를 virtual로 선언하고, 파생 클래스는 이름과 매개변수 목록이 일치하며 한정자와 반환형이 호환되는 함수를 제공합니다. 파생 클래스의 선언에 override를 표시하면 불일치가 컴파일 시 진단됩니다. 기본 클래스 참조나 포인터를 통해 함수를 호출하면 가상 디스패치가 해당 객체의 최종 오버라이더를 선택합니다.

다음 예제를 실행해 보세요.

이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.

#include <iostream>

class Message {
public:
    virtual void print() const {
        std::cout << "message\n";
    }
};

class Alert : public Message {
public:
    void print() const override {
        std::cout << "alert\n";
    }
};

int main() {
    Alert alert;
    const Message& message = alert;
    message.print();
}
alert

제공된 런타임 근거에 따르면 이 예제는 해당 출력을 내며 성공적으로 실행되었습니다. message의 형식은 기본 클래스이지만 실제로는 Alert를 가리키므로 가상 호출은 Alert::print를 사용합니다. 기본 클래스는 순수 가상 함수를 = 0으로 선언할 수도 있습니다. 이런 클래스는 추상 클래스이며, 구체적인 파생 클래스가 순수 가상 함수를 구현하기 전까지 인스턴스화할 수 없습니다.

오버로딩과 오버라이딩의 주요 차이점

측면 함수 오버로딩 함수 오버라이딩
목적 서로 다른 인수에 같은 이름의 연산 제공 파생 형식에 맞게 상속된 동작 구체화
요건 같은 범위에서 서로 다른 매개변수 목록 상속 관계와 기본 클래스의 일치하는 virtual 함수
선택 방식 호출 인수를 바탕으로 컴파일 시 오버로드 해결 기본 클래스 참조나 포인터를 통한 런타임 가상 디스패치
유용한 검사 호출이 의도한 오버로드를 선택하는지 확인 override를 추가해 선언의 일치 여부 확인

오버로드는 상속이나 가상 함수 없이도 만들 수 있습니다. 오버라이딩은 상속과 가상 디스패치에 의존하며, 인수 목록을 다르게 전달해서 선택되는 방식이 아닙니다.

흔한 실수와 알아보는 방법

파생 클래스에 같은 이름의 함수를 선언했다고 해서 자동으로 기본 클래스 함수를 오버라이드하는 것은 아닙니다. 기본 클래스 함수가 가상이 아니라면 파생 클래스의 선언은 가상 디스패치를 활성화하는 대신 기본 클래스의 이름을 숨깁니다. 매개변수, const 한정자 또는 함수 형식의 다른 부분이 일치하지 않으면 의도한 오버라이드가 성립하지 않을 수 있습니다. override를 작성하면 이런 불일치를 컴파일 시 확인할 수 있습니다.

반환형만 바꿔 오버로드를 만들려고 하지 마세요. 다형성을 기대하면서 파생 객체를 값으로 전달하는 것도 피해야 합니다. 파생 객체를 기본 객체에 복사하면 파생 부분이 잘려 나갑니다. 동적 디스패치를 유지하려면 기본 클래스 참조나 포인터를 사용하세요. 일반적인 소유권 관리에는 자동 객체나 스마트 포인터를 우선 사용하고, 기본 클래스 포인터로 파생 객체를 삭제해야 한다면 기본 클래스에 가상 소멸자가 있어야 합니다.