トラック
/
C++
C++
/
演習
/
トロールをからかう
トロールをからかう

トロールをからかう

学習演習

はじめに

列挙型

ここまでは、主に数値と文字列を扱ってきました。 現実の世界をモデル化するには、変数がとり得る値を限られた数だけにしたいことがあります。 それぞれに名前の付いた、いくつかの異なる値だけを持つ専用の型が欲しくなるかもしれません。 たとえばスケートボードの工場なら、デッキの素材をメープル、竹、プラスチックの3つだけから選ぶ、といった具合です。

こうした値を整数で表すこともできますが、素材としてシステムから不正な値が渡ってきていないかを確認するために、余分なコードを書く必要があります。 そうしたマジックナンバーは、ソースコードを追っても意味が分かりにくく、取り違えも起きやすくなります。 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;
}

スコープ

ホイールの素材を表す2つ目のenumerationがあるとします。

enum class WheelMaterial {
    steel,
    clay,
    plastic
};

ホイールとデッキはどちらも_plastic_で作ることができますが、この2つを混同することはありません。 両者は異なる_型_です。DeckMaterialのplasticとWheelMaterialのplasticです。 それぞれのenumerationは、enumeratorsを自前のスコープ、つまり自前のnamespaceの中に持ちます。 これが、scoped enumerationsと呼ばれる理由です。

Advanced

スコープなし列挙型

scopedという名前があるのなら、_unscoped_な列挙もありそうだと思ったかもしれません。そのとおりです。 Unscoped enumerationsは、すべてが同じグローバル名前空間を共有するため、あまり使われなくなってきています。 この共有のせいで、先ほどの例の_plastic_のように同じenumeratorsを持つunscoped enumerationsを2つ用意することはできません。

また、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を書くこともできます。 それぞれのcaseは、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から、問題を起こす人たちを分けて扱うための新しい権限システムの開発を頼まれました。

フォーラムでは、次の3つの操作ができます:

  • 読み取り
  • 書き込み
  • 削除

アカウントには4つの種類があり、それぞれ既定の権限が異なります:

  • ゲスト:投稿を読むことができます。
  • ユーザーとトロール:投稿を読むことと書き込むことができます。
  • モデレーター:投稿の読み取り、書き込み、削除ができ、すべての権限を持ちます。

Helmaは、トロールのアカウントを禁止しても意味がないことに気づきました。 彼女の作戦は、自分たちの時間が「有効に使われている」という錯覚を与えつつ、その投稿を他のトロールにだけ表示するというものです。 優先順位が必要な場面では、トロールは常に順番の最後になります。 ゲームに参加するときも、マッチングできるプレイヤーは他のトロールだけに限定されます。

この手法はシャドウバンと呼ばれます。

1. ユーザーの種類と権限を設定する

まず、4つのアカウントの種類を表すAccountStatus列挙型を定義します。種類はtroll、guest、user、modです。

次に、3つの権限の種類を表すAction列挙型を定義します。種類はread、write、removeです。

2. トロールの投稿は他のトロールにだけ表示する

フォーラムの各投稿は、投稿者のAccountStatusをメタデータに保存しています。 トロールによる投稿が、他のトロールにだけ表示されるようにしてください。

Helmaは、AccountStatus型の引数を2つ受け取り、boolを返すdisplay_post関数を必要としています。 1つ目の引数は投稿者のステータス、2つ目は閲覧者のステータスです。

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

3. ユーザーごとに権限を確認する

Helmaは、ある操作があるユーザーに許可されているかどうかを確認する方法を必要としています。

Actionを第1引数にとり、確認対象のAccountStatusをとるpermission_check関数を実装してください。 戻り値は、イントロダクションに書かれている権限に従ったboolにしてください。

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

4. ゲームへのアクセスを許可してプレイヤーを組み合わせる

ゲーム内の実際のプレイヤーに自分の行動の責任を持たせるため、_Hellmath_はゲストユーザーのアクセスを拒否します。 先ほども触れたとおり、Helmaはトロールに他のトロールをからかわせたいと考えています。 それ以外のユーザー同士のゲーム接続は制限されません。

2人のプレイヤーが同じゲームに参加できるかどうかを確認するvalid_player_combination関数を実装してください。 この関数はAccountStatus型の引数を2つとり、boolを返します。

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

5. 優先順位付きの待ち行列を作る

ゲームとフォーラムが大きく成長したため、Helmaは計算資源と帯域幅をユーザーに配分しなければならなくなりました。 緊急時に対応するため、モデレーターには最も高い優先順位が与えられます。 ゲストは一般ユーザーの後ろに並び、トロールは他の誰よりも後ろに回されます。

AccountStatus型の引数を2つとり、1つ目のアカウントの優先順位が2つ目より厳密に高い場合に限りtrueを返すhas_priority関数を実装してください。

has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
C++ Exercism

トロールをからかうを始める準備はできましたか?

Exercismに登録すれば、19個のコンセプト100個の演習、そして本物の人間によるメンタリングとともに、C++を学んでマスターできます。すべて無料です。