[C++] explicit 키워드

2022. 2. 12. 15:52Languages/C++

*이 글은 <씹어먹는 C++>을 참고하여 공부한 글입니다.

 

 

 

의도치 않은 암시적 변환(Implicit Conversion)

 

예를 들기 위해 간단하게 오늘도 몬스터(Monster) 클래스를 작성해봤다.

class Monster {
private:
    std::string name;
    int health;
    int attackDamage;
	
public:
    Monster();
    Monster(std::string name);
    Monster(const char* name);
    Monster(std::string name, int health, int attackDamage);
    void ShowInformation();
};

Monster::Monster() : Monster("", 0, 0) {}
Monster::Monster(std::string name) : Monster(name, 0, 0) {}
Monster::Monster(const char* name) : Monster(name, 0, 0) {}

Monster::Monster(std::string name, int health, int attackDamage) {
    this->name = name;
    this->health = health;
    this->attackDamage = attackDamage;
}

void Monster::ShowInformation() {
    std::cout << "<Monster Information>" << std::endl;
    std::cout << "Name : " << name << std::endl;
    std::cout << "HP : " << health << std::endl;
    std::cout << "AttackDamage : " << attackDamage << std::endl;
}

 

위와 같이 클래스 정보가 있고, 다음과 같이 몬스터 클래스를 인자로 받아 제거하는 함수를 로그를 띄우는 식으로 그냥 간단하게 만들었다.

void Destroy(Monster monster) {
    std::cout << "삭제된 몬스터 정보" << std::endl;
    std::cout << "-------------------------" << std::endl;
    monster.ShowInformation();
}

 

main() 함수에서 다음과 같이 실행을 했다.

int main() {
    Destroy("Slime");    // 암시적 변환 : Monster(const char* name) 생성자 호출됨
}

Destroy("Slime") 결과

 

 

분명 문자열 "Slime"만 전달했을 뿐인데, Monster("Slime") 객체가 생성된다. 함수에서 매개변수로 Monster 타입을 받기 때문에 C++ 컴파일러는 변환을 할만한 생성자가 없는지 살펴보기 때문이다. 이것을 암시적 변환(Implicit Conversion)이라고 한다.

 

암시적 변환이 나쁜 것은 아니지만, 가끔 예기치 못한 상황이 발생하기 때문에 오류를 잡기 어려울 수 있다.

예시를 보여주기 위해, 다음과 같이 클래스에 새로운 생성자를 추가해줬다.

class Monster {
private:
    std::string name;
    int health;
    int attackDamage;
	
public:
    Monster();
    Monster(int attackDamage);    // 추가 (이렇게만 생성자를 만들 일은 없겠지만 예시를 위해..)
    Monster(std::string name);
    Monster(const char* name);
    Monster(std::string name, int health, int attackDamage);
    void ShowInformation();
};


Monster::Monster() : Monster("", 0, 0) {}
Monster::Monster(std::string name) : Monster(name, 0, 0) {}
Monster::Monster(int attackDamage) : Monster("", 0, attackDamage) {}     // 추가
Monster::Monster(const char* name) : Monster(name, 0, 0) {}

 

 

그리고 다시 main() 함수에서 똑같은 코드를 실행해봤다.

int main() {
	Destroy(1);    // 오류 X : Monster(int attackDamage) 생성자 호출
}

Destroy(1) 결과

 

 

분명 함수를 잘못 사용한 것이 맞다. 정수 '1'를 삭제하는 연산이라는 것은 말이 안 되니까.

하지만, Monster 클래스 내부에 Monster(int attackDamage)라는 생성자가 있었기 때문에 오류가 나지 않고 암시적 변환을 C++ 컴파일러가 진행한 것이다.

 

이런 식으로 분명 프로그래머가 실수한 부분이지만 컴파일러가 오류를 내지 않기 때문에 알아차리기 어려울 수 있다.

이렇게 원하지 않는 암시적 변환을 허용하지 않는 방법으로 C++에서 explicit이라는 키워드를 제공한다.

 

 


explicit 키워드

 

  • 단어 뜻 그대로, 컴파일러에게 해당 클래스 멤버는 암시적 변환을 하지 않도록 명시할 수 있다.
  • 클래스 멤버에만 선언할 수 있다.
  • 복사 생성자의 형태로도 호출되는 것을 막는다.
class Monster {
private:
    std::string name;
    int health;
    int attackDamage;
	
public:
    Monster();
    explicit Monster(int attackDamage);    // explicit 키워드 추가
    Monster(std::string name);
    Monster(const char* name);
    Monster(std::string name, int health, int attackDamage);
    void ShowInformation();
};


Monster::Monster() : Monster("", 0, 0) {}
Monster::Monster(std::string name) : Monster(name, 0, 0) {}
Monster::Monster(int attackDamage) : Monster("", 0, attackDamage) {}
Monster::Monster(const char* name) : Monster(name, 0, 0) {}

...

 

그러고 난 뒤, main() 함수에서 똑같은 코드를 실행해보면 오류가 발생한다.

int main() {
    // Destroy(1);     오류 발생
}

컴파일러에게 암시적 변환을 허용하지 않도록 명시할 수 있다.

 

 

또한 복사 생성자의 형태로 호출하는 것 또한 막을 수 있다. explicit 키워드를 붙이지 않았다면, 다음 코드는 정상적으로 암시적 변환이 일어나서 잘 작동한다.

Monster m1 = "abc";       // Monster(const char* name) 호출
Monster m2 = 5;           // Monster(int attackDamage) 호출

m1.ShowInformation();
m2.ShowInformation();

 

 

허나, Monster(int attackDamage)explicit 키워드를 붙이면 오류가 난다.

Monster m1 = "abc";
// Monster m2 = 5;       오류

m1.ShowInformation();
// m2.ShowInformation();

 

 

 

728x90
반응형