트랙
/
C++
C++
/
연습 문제
/
트롤을 트롤하기
트롤을 트롤하기

트롤을 트롤하기

학습 연습 문제

소개

열거형

지금까지는 주로 숫자와 문자열을 다뤄 왔어요. 현실 세계를 모델링하려면 변수가 가질 수 있는 값을 몇 가지로 제한하고 싶을 때가 있어요. 서로 다른 이름을 가진 몇 개의 뚜렷한 값만 담는 전용 타입을 원할 수도 있고요. 예를 들어 스케이트보드 공장에서 데크 소재를 maple, bamboo, plastic 중에서만 고르게 하는 경우를 생각해 봐요.

그 값들을 인코딩하는 데 정수를 쓸 수도 있지만, 그러면 시스템에서 소재로 잘못된 값이 들어오는지 확인하는 추가 코드를 써야 해요. 이런 매직 넘버는 소스 코드 전체에서 그 의미를 따라가기가 어렵고, 헷갈리기 쉬워요. enumerations를 사용하면 표현력 있는 코드를 장려하고, 의도치 않은 비교 실수를 줄일 수 있어요.

사용 예

이런 종류의 열거형을 가리키는 구체적인 용어는 scoped enumeration이에요. 아래 코드는 DeckMaterial enumeration을 작성하는 방법을 보여줘요. enum class 키워드와 정의 끝의 ;에 주목해 보세요:

enum class DeckMaterial {
    maple,
    bamboo,
    plastic
};

이제 스케이트 가게의 가격 책정 함수를 보고, enumeration에서 enumerator를 지정하는 스코프 지정 연산자(::)에 주목해 봐요:

double deck_price(double base_price, DeckMaterial material) {
    if(material == DeckMaterial::plastic) {
        return base_price * 0.9;
    }
    return base_price * 1.3;
}

스코프

바퀴 소재를 위한 두 번째 enumeration이 있다고 상상해 봐요:

enum class WheelMaterial {
    steel,
    clay,
    plastic
};

바퀴와 데크가 둘 다 _plastic_으로 만들어질 수 있지만, 이 둘은 혼동될 수 없어요. 이들은 서로 다른 _타입_이에요. DeckMaterial의 plastic과 WheelMaterial의 plastic이에요. 각 enumeration은 자신만의 스코프, 즉 자신만의 namespace에 enumerators를 두게 돼요. 그래서 이들을 scoped enumerations라고 불러요.

Advanced

스코프 없는 열거형

scoped라는 이름이라면 unscoped 열거형도 있겠구나 하고 생각할 수 있어요. 맞아요. Unscoped enumerations는 모두 같은 전역 네임스페이스를 공유하기 때문에 점점 덜 쓰이고 있어요. 공유 때문에 위 예시의 _plastic_처럼 같은 enumerators를 가진 unscoped enumerations 두 개를 만들 수 없어요.

또한 unscoped enumerations는 암시적으로 정수로 변환돼요. 아래 예시에서 놀라운 결과를 확인해 봐요:

enum CitrusFruits {
    lemons,  // 0
    oranges, //1
};

enum IceCream {
    walnut,  // 0
    apples,  // 1
};

bool comparison{apples == oranges};
// => true

// Example from above:
bool comparison{DeckMaterial::plastic == WheelMaterial::plastic};
// => Does not compile!

scoped enumerations를 정수로 변환하고 싶다면 static_cast<int>를 사용하면 돼요.

switch문

다른 언어들처럼 C++도 switch문을 제공해요. switch문은 긴 if ... else if문을 더 짧게 쓰는 방법이에요. switch문을 만들려면 먼저 switch 키워드 뒤에 정수를 써요. 그다음 각 조건을 case 키워드로 선언해요. 이전 case 조건 중 어디에도 맞지 않을 때 실행되는 default 케이스도 선언할 수 있어요. 각 케이스는 break(또는 return)문으로 끝내야 해요.

int price{0};
int adults{3};
int kids{2};

switch (int group_size{adults + kids}) {
    case 1:
        price = 50;
        break;
    case 2:
        price = 70;
        break;
    default:
        price = group_size * 30;
}

폴스루

switch 구조에서 중요한 점은, break(또는 return)문을 만나 멈출 때까지 코드가 계속 실행된다는 거예요. 이로 인해 예상치 못한 동작이 생길 수 있어요.

int adults{1};
int kids{0};
switch (int group_size{adults + kids}) {
    case 1:
        price = 50;
    case 2:
        price = 70;
    default:
        price = group_size * 30;
}
// price will be 30!

이렇게 계속 실행되는 기능의 주된 용도는 여러 레이블을 가진 문이에요. 여러 switch 결과를 같은 실행 코드로 연결할 수 있어요. 이렇게 하면, 예를 들어 예약 앱에서 인원이 2명과 3명일 때 호출되는 함수를 같게 만들 수 있어요:

switch (group_size) {
case 1:
    book_room();
    break;
case 2:
case 3:
    book_apartment(group_size);
    break;
default:
    book_house(group_size);
}
// book_apartment happens when group_size is 2 or 3

지침

친구인 Helma는 빠르게 인기를 얻은 작은 온라인 게임을 하나 만들었어요. 게임 이름은 _HellMath_예요. 작은 커뮤니티에 트롤들이 모여들면서 게임과 포럼 분위기가 꽤 불쾌해졌어요. Helma는 문제를 일으키는 사람들을 분리하기 위한 새로운 권한 시스템 작업을 부탁했어요.

포럼에서는 세 가지 동작을 지원해요:

  • Read
  • Write
  • Remove

계정 유형은 네 가지이고, 각각 기본 권한이 달라요:

  • 게스트: 게시물을 읽을 수 있어요.
  • 사용자와 트롤: 게시물을 읽고 쓸 수 있어요.
  • 관리자: 게시물을 읽고, 쓰고, 삭제할 수 있으며 모든 권한을 가지고 있어요.

Helma는 트롤 계정을 차단하는 것이 소용없다는 걸 알아챘어요. 그녀의 전략은 트롤에게 자신의 시간이 "잘 투자되고 있다"는 착각을 심어 주되, 그들의 게시물은 다른 트롤에게만 보이게 하는 것이에요. 우선순위 정렬이 필요한 경우, 트롤은 항상 순서에서 가장 뒤에 있어요. 트롤이 게임에 들어가면, 함께 플레이할 수 있는 상대도 다른 트롤로 제한돼요.

이런 방식을 섀도 밴이라고 해요.

1. 사용자 유형과 권한을 설정해요.

먼저 네 가지 계정 유형인 troll, guest, user, mod를 나타내는 AccountStatus 열거형을 정의해요.

다음으로 세 가지 권한 유형인 read, write, remove를 나타내는 Action 열거형을 정의해요.

2. 트롤의 게시물은 다른 트롤에게만 보여줘요.

포럼의 모든 게시물은 메타데이터에 작성자의 AccountStatus를 저장해요. 트롤이 작성한 게시물은 다른 트롤에게만 표시되도록 해요.

Helma는 AccountStatus 인자 두 개를 받아서 bool을 반환하는 display_post 함수가 필요해요. 첫 번째 인자는 게시물 작성자의 상태이고, 두 번째 인자는 게시물을 보는 사람의 상태예요.

using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true

3. 사용자별 권한을 확인해요.

Helma는 어떤 동작이 특정 사용자에게 허용되는지 확인할 방법이 필요해요.

Action을 첫 번째 인자로 받고, 확인할 대상인 AccountStatus를 두 번째 인자로 받는 permission_check 함수를 구현해요. 이 함수는 앞에서 설명한 권한에 따라 bool을 반환해야 해요.

permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true

4. 게임 접근 권한을 주고 플레이어를 짝지어요.

게임에 참여한 실제 플레이어가 자신의 행동에 책임을 지도록, _Hellmath_는 게스트 사용자의 접근을 막아요. 앞서 말했듯이 Helma는 트롤이 다른 트롤들 사이에서만 활동하길 바라요. 그 외 사용자들 사이의 게임 연결은 제한이 없어요.

두 플레이어가 같은 게임에 참여할 수 있는지 확인하는 valid_player_combination 함수를 구현해요. 이 함수는 AccountStatus 타입의 매개변수 두 개를 받고 bool을 반환해요.

valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true

5. 우선순위 큐를 만들어요.

게임과 포럼이 크게 성장하면서, Helma는 이제 사용자들에게 컴퓨팅 자원과 대역폭을 분배해야 해요. 긴급한 상황을 처리하기 위해 관리자에게 가장 높은 우선순위가 주어져요. 게스트는 일반 사용자 뒤에, 트롤은 모든 사람보다 더 뒤에 배치돼요.

AccountStatus 인자 두 개를 받아서, 첫 번째 계정이 두 번째 계정보다 우선순위가 엄격하게 높은 경우에만 true를 반환하는 has_priority 함수를 구현해요.

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
C++ Exercism

트롤을 트롤하기 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 C++ 트랙을 개념 19개연습 문제 100개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.