C++ 함수 키워드 (default, delete, override, final)

Seongcheol Jeon·2024년 12월 7일
0

CPP

목록 보기
38/47
post-thumbnail

함수를 만들 때 사용할 수 있는 키워드를 알아보자. 이 키워드들은 함수를 만든 목적과 사용 방법을 명확히 함으로써 개발자 본인은 물론이고 공동 작업자나 라이브러리 사용자가 정확히 이해하고 사용할 수 있도록 돕는다.

함수의 목적과 사용 방법을 알리는 가장 확실한 방법은 잘못 사용했을 때 컴파일되지 않도록 하는 것이다.

delete, default, override, final 그리고 noexcept 키워드를 이용하면 함수의 목적을 쉽게 파악할 수 있게 하고 잘못된 사용을 문법적으로 막을 수 있다.

default 키워드 - 기본으로 제공되는 함수 사용

default 키워드는 컴파일러가 제공하는 기본 함수를 사용하겠다는 의미이다. default 키워드를 사용하면 생성자의 경우, 매개변수가 없는 기본 생성자가 호출되고, 연산자는 오버로딩되지 않은 기본 연산자가 호출된다.

생성자를 예로 들어보자. 개발자가 클래스를 만들 때 생성자를 작성하지 않으면 매개변수가 없는 기본 생성자가 자동으로 생성된다. 하지만 매개변수를 포함하는 생성자를 작성하면 이 생성자만 사용할 수 있다. 즉, 생성자를 만들었으므로 매개변수가 없는 생성자는 자동으로 생성되지 않는다.

그런데 자동으로 생성된 기본 생성자를 사용하다가 매개변수를 포함하는 생성자를 추가한다면 기본 생성자를 사용할 수 없게 되므로 기본 생성자가 호출되는 코드에 문제가 발생한다. 또는 컴파일러가 만들어 주는 기본 생성자와 매개변수를 포함하는 생성자를 모두 사용하고 싶을 수 있다.

이럴 때에 default 키워드를 사용할 수 있다.

default 키워드는 컴파일러가 자동으로 만들어 주는 매개변수가 없는 기본 생성자를 사용하겠다고 알리는 방법이다.

#include <iostream>
#include <compare>

using std::cout;
using std::endl;

class monster_a {
public:
    // 컴파일러 또는 언어가 제공하는 기본 생성자 사용
    monster_a() = default;
    monster_a(int init_hp, int init_power)
        : hp(init_hp), power(init_power) {
    }
    int hp, power;
};

class monster_b {
public:
    // 아무런 연산도 하지 않는 기본 생성자 정의
    monster_b() {}
    monster_b(int init_hp, int init_power)
        : hp(init_hp), power(init_power) {
    }
    int hp, power;
};

class monster_c {
public:
    // 매개변수 없는 기본 생성자 정의하지 않음
    monster_c(int init_hp, int init_power)
        : hp(init_hp), power(init_power) {
    }
    int hp, power;
};


int main()
{
    // 기본 생성자로 객체 생성
    monster_a mon_a{};

    // 아무런 연산도 하지 않는 기본 생성자로 객체 생성
    monster_b mon_b{};

    // monster_c에 매개변수가 없는 기본 생성자가 없어, 객체 생성에 실패하여 컴파일 에러 발생
    //monster_c mon_c{};

    cout << "monster_a: " << mon_a.hp << ", " << mon_a.power << endl;
    cout << "monster_b: " << mon_b.hp << ", " << mon_b.power << endl;
    
    return 0;
}

실행 결과

monster_a: 0, 0
monster_b: -858993460, -858993460

monster_c 클래스에는 매개변수 없는 기본 생성자가 정의되지 않아 객체 생성이 안된다. monster_b 클래스에는 아무런 연산도 하지 않는 기본 생성자를 직접 정의했다. 얼핏 보면 차이가 없어 보이지만 그렇지 않다.

객체를 생성할 때 monster_a mon_a{} 처럼 중괄호 ({})를 사용하면 C++11에서 추가된 멤버 변수를 초기화하는 기본 생성자를 호출한다. 실행 결과를 보면 기본 생성자로 만든 monster_a의 객체는 정수형 멤버 변수값이 0으로 초기화되었지만, 아무런 연산도 하지 않는 기본 생성자로 만든 monster_b의 객체는 쓰레기 값이 입력된 것을 알 수 있다.


delete 키워드 - 삭제된 함수

delete 키워드는 더 이상 사용하지 않는 함수에 붙이는 키워드이다. 앞에서 함수의 목적과 사용 방법을 알리는 가장 확실한 방법은 잘못 사용했을 때 컴파일되지 않도록 하는 것이라고 했다. 문법이 틀리면 컴파일 오류가 나는 것처럼 개발 의도와 다르게 사용할 때 컴파일 오류가 발생하도록 하는 것이다.

소스 코드에서 함수가 변경되거나 삭제되면 호출 오류가 발생한다. 매개변수나 함수 이름의 변경은 매개변수 구성이 바뀌는 것이므로 그나마 쉽게 이해할 수 있지만, 삭제된 함수는 그 의도를 파악하기가 어렵다. 삭제된 것이 아니라 함수 이름이 바뀌거나 실수로 누락된 것일 수도 있기 때문이다.

이때 함수가 더 이상 사용되지 않고 삭제되었음을 알려 주는 키워드가 바로 delete이다.

다음 코드는 delete 키워드를 사용한 경우와 그냥 삭제한 함수를 호출했을 때 오류 메시지를 비교한 예이다.

#include <iostream>

using std::cout;
using std::endl;

class monster {
public:
    void create_monster() = delete;

    // delete 키워드를 사용하지 않고 제거
     //void create_monster_() {};
};


int main()
{
    monster monster_sample;

    monster_sample.create_monster();
    //monster_sample.create_monster_();
    
    return 0;
}

실행 결과

note: 'void monster::create_monster(void)': function was explicitly deleted

두 경우에 대한 오류 메시지가 다를 것이다. delete 키워드를 사용했을 때는 삭제 된 함수를 참조할 수 없다고 명확한 메시지를 보여준다. 하지만 delete 키워드를 사용하지 않고 그냥 삭제한 함수는 멤버가 없다는 오류가 발생한다.

이처럼 함수가 존재하지 않을 때와 delete로 표기했을 때 오류가 다른 것을 프로그램을 개발할 때에 활용할 수 있다.

함수의 시그니처나 이름을 변경할 때 기존 함수에 delete 키워드를 붙이면, 변경 전 함수를 사용하던 개발자가 컴파일 오류를 통해서 사용 중인 함수가 제거되었음을 알 수 있다.


override 키워드 - 재정의 함수

가상 함수는 객체지향 언어의 주요 특징인 다형성C++ 언어에서 구현할 수 있도록 해주는 문법이다. 그런데 클래스 상속이 반복되다 보면 가상 함수 재정의를 잘못하는 실수를 할 수 있다.
실수로 다른 이름이나 다른 매개변수를 사용하는 것인지, 가상 함수를 상속받는 과정에서 발생한 실수인지 컴파일러는 명확하게 구별하지 못할 수 있다.

이럴 때, override 키워드를 사용할 수 있다. override 키워드를 사용해 가상 함수를 상속받았음을 명확히 하는 것이다. 이때 가상 함수 상속 규칙에 어긋나는 오류가 있다면 컴파일 오류가 발생한다.

즉, 오버라이딩인지 아닌지를 컴파일러가 판단할 수 없을 때 판단할 수 있는 기준을 제시해 주는 것이다.


final 키워드 - 재정의 금지

만약 더 이상 재정의하지 않도록 지정하고 싶다면 어떻게 해야 할까? 특ㅈ덩 시점에 가상 함수의 재정의나 클래스, 구조체의 상속을 막으려면 최종 상속 단계에서 final 키워드를 추가하면 된다.

final 키워드가 추가된 가상 함수나 클래스 등은 더 이상 상속할 수 없다. 만약 재정의나 상속을 시도하면 컴파일 오류가 발생하여 상속할 수 없음을 알려 준다. 이로써 제공자의 의도와 다르게 가상 함수가 변경되는 것을 막을 수 있다. 다만 클래스나 가상 함수는 final 키워드로 재정의나 상속을 막을 수 있지만, 일반 함수에는 사용할 수 없으니 주의해야 한다.

override, final 사용법은 간단하다. 오버라이드로 재정의한 함수의 선언부 가장 마지막에 해당 키워드를 적어 주면 된다.

// 재정의된 가상 함수임을 알림
virtual function(...) override {};

// 재정의할 수 없음을 알림
virtual function(...) final {};

// 클래스를 더 이상 상속할 수 없음을 알림
class derivate_class final : base_class {};

// 구조체를 더 이상 상속할 수 없음을 알림
struct derivate_struct final : base_struct {};

0개의 댓글