トラック
/
C++
C++
/
演習
/
トロイの力
トロイの力

トロイの力

学習演習

はじめに

C++のautoキーワード

C++のautoキーワードは、C++11で導入された強力な機能で、型を推論して変数を宣言するために使います。 コンパイラーは初期化子に基づいて変数の型を推論するので、コードが読みやすく、保守しやすくなります。

使用例

次の例を見てみましょう。ここではautoを使って変数を宣言しています。

auto dragon_population{3};          // dragon_population is deduced as an integer
auto westeros{7.7777};              // westeros is deduced as a double
auto wedding_location{"The Twins"}; // wedding_location is deduced as a const char*, not std::string

いずれの場合も、変数の型は初期化に使った値から推論されます。

型推論

autoキーワードは、明示的な型の冗長さを減らし、より簡潔で読みやすいコードを書くのに役立ちます。

const std::vector<std::string> pigeon_pie{"flour", "butter", "pigeon", "salt"};
auto purple_wedding_pie{pigeon_pie};
purple_wedding_pie.emplace_back("the strangler");

このループでは、autoがpurple_wedding_pieの型をstd::vector<std::string>と推論するので、型をもう一度明示的に指定する必要がありません。

互換性

autoキーワードはさまざまなC++の構文と組み合わせて使えるので、現代のC++プログラミングで万能な道具になります。

auto& element{array[0]};           // reference to an element
const auto object{otherObject};    // const type version of otherObject's type
auto* ptr{&x};                     // pointer to x with the same type as x, but as a pointer.

この後の概念では、ラムダ式、範囲ベースのforループ、イテレーターと一緒にautoキーワードをよく目にすることになります。

スマートポインター

スマートポインターは、自動的なメモリ管理を提供するために設計された現代C++の機能で、生のポインターにありがちなメモリリークやダングリングポインターを防ぐのに役立ちます。 生のポインターを包み込むラッパーとして働き、ポインターが不要になったときの自動的なメモリ解放といった追加の機能を付け加えます。

基本的な構文

スマートポインターは通常、C++標準ライブラリでクラステンプレートとして実装されています。 もっともよく使われるスマートポインターは、std::unique_ptrとstd::shared_ptrです。

ユニークポインター

std::unique_ptrは、オブジェクトを独占的に所有するスマートポインターです。 いつでも、リソースを所有するstd::unique_ptrオブジェクトは1つだけであることを保証します。 所有しているstd::unique_ptrが破棄またはリセットされると、そのオブジェクトを自動的に破棄し、メモリを解放します。

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

std::make_unique()の利点

std::unique_ptrを作るときは、newを直接使ってメモリを確保するよりも、std::make_unique()を使うのが好ましいです。 std::make_unique()には、いくつかの利点があります。

  1. 例外安全性: std::make_unique()は例外安全性を保証します。 オブジェクトの構築中に例外が投げられた場合、メモリは自動的に解放され、メモリリークを防ぎます。
  2. 明確さ: std::make_unique()を使うと、コードがより明確で簡潔になります。 テンプレート引数が自動的に推論されるので、確保する型を明示的に指定する必要がありません。
  3. 最適化の余地: コンパイラーは、newで手動でメモリを確保するよりもstd::make_unique()を効果的に最適化できる余地があり、パフォーマンスが向上する可能性があります。
  4. 誤用の回避: std::unique_ptrを手動で構築した場合、その基になるリソースを削除できてしまいます。 std::unique_ptrがスコープの終わりにそれを削除しようとすると、未定義動作につながります。

共有ポインター

std::shared_ptrは、複数のstd::shared_ptrオブジェクトが同じリソースの所有権を共有できるスマートポインターです。 リソースを参照している共有ポインターの数を数えておき、そのリソースを所有する最後の共有ポインターがスコープを抜けるか、リセットされたときにだけメモリを解放します。

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

C++17以前では、std::make_shared<T[]>を使って配列をstd::shared_ptrで扱うことは直接にはサポートされていません。 std::make_shared<T[]>で配列を確保することは可能ですが、そこから直接共有ポインターを作ると、単一のオブジェクトと配列のメモリ管理の違いから、未定義動作につながる可能性があります。 代わりに、共有ポインターで配列を効果的に管理するには、std::vectorやカスタムの削除関数を使うことを検討しましょう。 C++17で配列の確保と共有ポインターを扱うときは、いつも使っているコンパイラーと標準ライブラリの実装との互換性を確認しましょう。

std::make_shared()の利点

std::make_unique()と同様に、std::make_shared()には、メモリ効率の向上、例外安全性、読みやすさといった利点があります。 コントロールブロックと管理対象オブジェクトのメモリ確保を1つの操作にまとめるので、効率が上がり、メモリリークのリスクが減ります。 さらに、テンプレート引数が自動的に推論されることで、コードが簡潔になり、読みやすさも向上します。 C++でstd::shared_ptrオブジェクトを扱うとき、std::make_shared()を使うと、よりクリーンで安全、かつ効率的なコードになります。

使い方の指針

基本的にはスマートポインターを使いましょう。独占的な所有にはstd::unique_ptr、共有の所有にはstd::shared_ptrです。 生のポインターは、所有権を持たない参照や、レガシーコードとやり取りするときに取っておきましょう。 ほとんどの場合、独占的な所有にはstd::unique_ptrで十分です。参照カウントのオーバーヘッドなしに、軽量なメモリ管理を提供してくれるからです。 本当に共有の所有が必要でない限り、std::shared_ptrは控えめに使うべきです。オーバーヘッドと複雑さを持ち込むからです。

説明

この演習では、トロイの世界を舞台にします。 そこに暮らす人々の生活は、驚きと魔法に満ちています。 トロイに住む人間の多くは_力_を備えており、それを日常生活の中で頻繁に使います。 力は、世界を作り変えたり、トロイの動物や他の人々に影響を与えたりするために使われます。 また魔法は_唯一無二のアーティファクト_にも宿り、冒険者や職人、賢者たちがこぞって探し求めるものです。

この演習では、トロイの人間、彼らが持つアーティファクト、そして力の相互作用をモデル化するコードを書きます。

課題は6つです。 最初の1つは人間の作成に関するもので、残りの5つは力とアーティファクトの扱いに関するものです。

1. トロイの世界に人間を誕生させる

トロイのモデルでは、人間が最も重要な要素です。 モデルとなる人間は、_唯一無二のアーティファクト_を所有できる必要があります。 また、_力_を顕現させる能力も持っている必要があります。 これらの力は他の人間に影響を与えることがあるため、ある人間が別の力の影響を受けているかどうかもモデル化します。

artifact構造体とpower構造体の基本的な実装はすでに用意されています。 possessionという名前のartifactメンバー変数への_スマートポインター_を持つhuman構造体(またはクラス)を実装します。 各アーティファクトは、いかなる時点でも1人の人間しか所有できません。

humanには、さらに2つのメンバー変数が必要です。 1つはその人のown_powerを保持し、もう1つはその人がinfluenced_byである力を表します。 own_powerとinfluenced_byはどちらもpowersへの_スマートポインター_です。 各powerは1人の人間に所有されつつ、同時に他の人間にも影響を与えることがあります。

デフォルトでは、人間はアーティファクトを一切持たずに生まれ、力も所有しておらず、その影響も受けていません。

human mindy_mccready{};
mindy_mccready.possession;
// => nullptr
mindy_mccready.own_power;
// => nullptr
mindy_mccready.influenced_by;
// => nullptr

2. トロイにアーティファクトをもたらす

各部分が相互作用しなければ、モデルは退屈なものになってしまいます。 唯一無二のアーティファクトを作成し、それを特定の人間に与えられるようにします。

humanへの参照とstringを受け取り、何も返さない関数give_new_artifactを定義します。 このstringを使って新しいartifactオブジェクトを定義し、それに応じてhumanのpossessionポインターを設定します。 この関数は何も返しません。

human erik_magnus_lehnsherr{};
give_new_artifact(erik_magnus_lehnsherr, "Mind shielding helmet");

erik_magnus_lehnsherr.possession->name;
// "Mind shielding helmet"

3. アイテムを交換できるようにする

トロイの世界は、何よりも相互作用が大切です。 人々が自分たちの所有物を交換して取引できるようにします。

アイテムを交換するために、2つのアーティファクトのスマートポインターを受け取り、何も返さない関数exchange_artifactsを書きます。 unique_ptrはコピーできないことを忘れないでください。 これは関数の引数での使用にも当てはまります。 代わりにunique_ptrへの参照を使います。

human uchiha{};
give_new_artifact(uchiha, "konoha headband");
human uzumaki{};
give_new_artifact(uzumaki, "forehead protector");

exchange_artifacts(uchiha.possession, uzumaki.possession);

uchiha.possession->name;
// "forehead protector"
uzumaki.possession->name;
// "konoha headband"

4. 人々に力を与える

トロイで最も胸躍る要素は、人々が操る特別な力です。 ある者は念じるだけで鉄を精錬し、またある者は夜になるとあらゆる傷を瞬時に癒やします。

humanへの参照とstringを受け取り、何も返さない関数manifest_powerを定義します。 このstringを使って新しいpowerオブジェクトを定義し、それに応じてhumanのown_powerポインターを設定します。 この関数は何も返しません。

human eleven {};
manifest_power(eleven, "psychokinesis");

eleven.own_power->effect;
// "psychokinesis"

5. 力を使う

どんなに偉大な力も、使えなければ意味がありません。 モデルは人間を中心に据えているため、力の影響を追跡できるようにします。

2つの人間への参照を取る_void_関数use_powerを書きます。 1人目は力をかける側で、2人目は対象を表します。 対象のinfluenced_byポインターは、かけた側の力を指すようにします。

単純化のため、人間は同時に1つの力からしか影響を受けられないものとします。 かけた側が存在しなくなっても、この力はそのまま残ります。

human pamela_isley{};
manifest_power(pamela_isley, "control pheromones");

human count_vertigo{};
use_power(pamela_isley, count_vertigo);
count_vertigo.influenced_by->effect;
// "control pheromones"

6. 力の強さを監視する

特定の力は、複数の人間に及ぼされると、効力を失ったり、シミュレーション内で特定の効果を引き起こしたりします。 各力につながっている人の数を追跡できるようにします。

人間を受け取り、その人の力の強さを_int_として返す関数power_intensityを定義します。 その人に力がなければ、戻り値は0です。 そうでなければ、強さはかけた本人と、現在影響を受けているすべての人を反映します。

human jean_grey{};
manifest_power(jean_grey, "uplifting personality");

human scott{};
human logan{};
human ororo{};

use_power(jean_grey, ororo);
use_power(jean_grey, logan);
use_power(jean_grey, scott);

power_intensity(jean_grey);
// 4
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
C++ Exercism

トロイの力を始める準備はできましたか?

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