他の多くの言語と同じように、C++にもポインターがあります。 参照とポインターが似ていることはもう知っているはずです。ただ、ポインターはコンピューターの内部動作にもう一歩近づいたものだと考えてください。 ポインターは、オブジェクトのアドレスを保持する変数です。 ポインターを使うとオブジェクトを直接操作でき、C++における動的なメモリ確保や効率的なデータ操作が可能になります。
ポインターに初めて触れると、少し不思議に感じるかもしれません。でも、慣れてしまえばとても素直な仕組みです。
C++を使ううえで欠かせない要素なので、じっくり時間をかけて理解しましょう。 このコンセプトで扱う最小限の形は、ダムポインターや生ポインターとも呼ばれます。 現代のC++にはスマートポインターもありますが、基本の型は決してスマートではなく、作業はすべて手動で行う必要があります。
詳細に入る前に、ポインターの用途を理解しておく価値があります。 ポインターは、オブジェクトのアドレスをプログラムの他の部分と共有する手段であり、次の2つの大きな理由から役立ちます:
C++でポインターを宣言するときは、ポインターが指すデータ型を指定し、その後にアスタリスク(*)とポインター名を書きます。
ポインターは、宣言しただけでは自動的に初期化されません。
明示的に代入しない場合、ポインターはたいてい不定の値、いわゆる「ゴミアドレス」を保持します。
コンパイラーによってはポインターをnullptrに初期化することもありますが、その動作がすべてのコンパイラーで保証されているわけではないので、当てにしてはいけません。
問題を避けるために、生ポインターは明示的に初期化し、使う前にnullでないことを確認するのが良い習慣です。
int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid
変数のアドレスをポインターに代入するには、アドレス演算子(&)を使います。
ポインターの参照外しは、間接演算子(*)を使って行います。
std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};
注意:参照外しは明示的に行う必要がありますが、参照はエイリアスのようにそのまま使えました。
ポインター演算を使うと、ポインターに対して算術演算を行えます。これは配列を扱うときに特に便利です。 ポインターに整数を足すと、別の要素を指すようになります。
// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
C++のポインター演算は、注意深く扱わないと簡単に__未定義動作__を引き起こします。 未定義動作は、予期しないプログラムの動作、クラッシュ、さらにはセキュリティ脆弱性として現れることがあります。 未定義動作が引き起こした結果として悪名高い例に、1996年のアリアン5ロケットの爆発があります。そこでは、64ビット浮動小数点数を16ビット符号付き整数に変換したことで生じたソフトウェアの例外が、壊滅的な失敗につながりました。
C++では、->演算子を使って、オブジェクトへのポインターを通じてそのオブジェクトのメンバーにアクセスします。
これは、ポインターが指すオブジェクトのメンバーにアクセスするための簡略記法です。
たとえば、ptrがメンバー変数xを持つオブジェクトへのポインターである場合、(*ptr).xと書く代わりに、ptr->xと直接書けます。
この演算子を使うと、オブジェクトへのポインターを扱うときにコードが読みやすくなり、記述も短くなります。
簡単な例を見てみましょう。メンバー変数superpowerを持つstructのSuperheroを考えます。
main関数は、Superheroオブジェクト(ワンダーウーマンを表します)へのポインターdianaPrinceを作成します。
->演算子を使ってメンバー変数superpowerにアクセスし、ワンダーウーマンの象徴である「真実の投げ縄」を表現しています。
struct Superhero {
std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;
ポインターと参照はどちらもオブジェクトへの間接的なアクセスを可能にしますが、できることと安全性の面で違いがあります。 ポインターは、指す対象を自由に変更でき、nullを代入することもできます。 しかし、この柔軟さには、nullポインターの参照外しやダングリングポインターの生成といったリスクが伴います。 一方、参照はnullになれず、作成時に有効なオブジェクトに束縛されるため、こうしたリスクを避けられます。 このように安全性が高いことから、ポインターが提供する追加の機能が必要でない限り、ポインターよりも参照を選ぶべきです。
ようこそ、エンジニア! スピーディワゴン財団に残る、最後のベテランの一人です。この財団は、何十年もの間、柱の男のような古の脅威と戦ってきた秘密組織です。 その活動の中で、最先端の技術と古びたライブラリを組み合わせて作られた財団の技術システムの保守に、何年も費やしてきました。
しかし、最近では、柱の男の活動を追跡するセンサーが誤作動を起こしています。 財団のシステムは古く、そのコードは更新できないレガシーなC++ライブラリと連携しています。 ここでの任務は、古風なポインタベースのライブラリを使って、柱の男センサーの活動を監視する4つの中核となる関数を実装することです。
現代のC++エンジニアなら、スマートポインタを使いたいところでしょう。しかし残念ながら、レガシーコードは古い流儀に敬意を払うことを求めてきます。 人類の命運はこのポインタにかかっているかもしれません。慎重に進めてください。波紋エネルギーが導いてくれますように。
センサーの測定値は膨大になることがあるため、実際のライブラリで使われるモックアップの_構造体_を用意しています。 コードはすでにヘッダーファイルに実装済みです。
struct pillar_men_sensor {
int activity{};
std::string location{};
std::vector<int> data{};
};
connection_check)最初のタスクは、柱の男センサーが正しく接続されていることを確認することです。
接続されていないセンサーによる誤報は避けなければなりません。
connection_check関数を書きます。この関数は、nullptrかどうかを調べることで、センサーのポインタが有効かどうかを判定します。
pillar_men_sensor_構造体_へのポインタを入力として受け取る関数を定義します。trueを、そうでなければfalseを返します。pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false
activity_counter)柱の男は影に潜んでいます。センサーが何らかの活動を検知したかどうかを知る必要があります。
activity_counter関数を書きます。この関数は、センサーの配列と、配列内のセンサーの数を示す容量を受け取ります。
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123
alarm_control)すべてのセンサーがアラームを鳴らせばよいわけではありません。本当の危険があるときだけ鳴らすべきです。
alarm_control関数は、活動レベルが0より大きい場合にだけ、センサーがアラームを鳴らすようにします。
また、この関数は、システムのクラッシュを防ぐために、nullのセンサーもチェックする必要があります。
pillar_men_sensorへのポインタを入力として受け取る関数を定義します。nullptrかどうかをチェックします。センサーがnullptrならfalseを返します。trueを返し、そうでなければfalseを返します。pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true
uv_alarm関数でデータの異常を調べるこのタスクでは、紫外線の照射レベルとセンサーの活動に基づいて、アラームを鳴らすべきかどうかを判断するuv_alarm関数を実装します。
uv_alarm関数は、用意されているuv_light_heuristic関数を使います。この関数はデータのベクトルを処理し、特定のしきい値に基づいた値を返します。
これは、本番環境で動く複雑なコードのモックアップ版です。インターフェースは変更しないでください。
speedywagon名前空間にuv_alarm関数を定義します。動作は次のとおりです。
pillar_men_sensor_構造体_へのポインタを入力として受け取ります。falseを返します。data配列のアドレスを渡して、uv_light_heuristic関数を呼び出します。uv_light_heuristicが返した値がsensor->activityのレベルより大きければtrueを返し、そうでなければfalseを返します。