Articles

C++ ポインタ:なぜ必要か、いつ使うか、オブジェクト自体へのアクセスとどう違うか

ほとんどのプログラマはオブジェクトとそのポインタに何が違うかを理解していますが、時には、どのアクセス方法を選択すればよいか、まったくわからないことがあります。

Question

私は Java のバックグラウンドを持っていますが、C++ でオブジェクトを扱うようになりました。 たとえば、この宣言:

Object *myObject = new Object;

ではなく、:

Object myObject;

またはこのように関数、たとえば testFunc() を使う代わりに:

myObject.testFunc();

と書く必要があるのですが、なぜこの方法で行う必要があるかがわかりません。 メモリアドレスに直接アクセスできるので、効率と速度に関係していると推測されます。 たとえば、プログラマがコード内のオブジェクトにポインタを介してアクセスすることはできません。 しかし、Javaではbaseを除くすべての型が参照され、それらへのアクセスはリンクによって行われます。

C++ のポインタについて少し理解するために、2 つの類似したコード断片を示します。

C++:

Object * object1 = new Object(); //A new object is allocated on the heapObject * object2 = new Object(); //Another new object is allocated on the heapdelete object1;//Since C++ does not have a garbage collector, //if we don't do that, the next line would //cause a "memory leak", i.e. a piece of claimed memory that //the app cannot use //and that we have no way to reclaim...object1 = object2; //Same as Java, object1 points to object2.

別の C++ 方法を見てみましょう。

Object object1; //A new object is allocated on the STACKObject object2; //Another new object is allocated on the STACKobject1 = object2;//!!!! This is different! //The CONTENTS of object2 are COPIED onto object1,//using the "copy assignment operator", the definition of operator =.//But, the two objects are still different. //Change one, the other remains unchanged.//Also, the objects get automatically destroyed //once the function returns...

メモリに直接アクセスすることによって、速度が向上するのでしょうか。 ポインターは通常、ヒープへのアクセスに使用され、オブジェクトはスタックに配置されます – これは、よりシンプルで迅速な構造です。 初心者の方には、スタックとヒープが何であるかを詳細に説明した資料があります。 1 つ目は、いつ動的メモリ割り当てを使用するかです。 2 つ目は、いつポインターを使用するのがよいのか、ということです。 確かに、私たちは、常に仕事に最も適したツールを選択する必要があるという一般的な言葉なしに行うことはありません。 ほとんど常に手動動的割り当て(ダイナミックアロケーション)および/または生のポインターを使用するよりも良い実現があります。

あなたはそれほど頻繁に動的割り当てを見ることは非常に残念なことです。 それは、いかに多くの悪い C++ プログラマーがいるかを示しています。

ある意味で、あなたは 2 つの質問を 1 つに束ねて持っています。 1 つは、いつ動的割り当てを使用すべきか (new を使用)、もう 1 つは、いつ動的割り当てを使用すべきか (new を使用)、です。 もう 1 つは、いつポインターを使用すべきかです。

重要な留意点は、仕事には常に適切なツールを使用すべきであるということです。 ほとんどすべての状況で、手動で動的割り当てを実行したり、生のポインターを使用したりするよりも、より適切で安全なものがあります。

Dynamic allocation

あなたの質問では、オブジェクトを作成する 2 つの方法が示されています。 主な違いは、オブジェクトの保存期間です。 ブロック内でオブジェクト myObject; を実行すると、オブジェクトは自動保存期間で作成され、スコープ外に出ると自動的に破棄されます。 一方、new Object()を実行すると、オブジェクトは動的保存期間となり、明示的に削除されるまで生き続けます。 動的な保存期間は、必要なときだけ使用するようにしましょう。

動的割り当てが必要になる主な 2 つの状況:

  1. オブジェクトを現在のスコープより長持ちさせる必要がある場合。 オブジェクトのコピー/移動に問題がない場合 (ほとんどの場合、そうあるべきです)、自動オブジェクトを好むべきです。
  2. 多くのメモリを割り当てる必要があり、簡単にスタックをいっぱいにすることができます。 C++ の範囲外であるため、これを気にする必要がない (ほとんどの場合、気にする必要はない) のは良いことですが、残念ながら、私たちが開発しているシステムの現実に対処しなければなりません。 ご存知のように、C++では配列のサイズは固定されています。 例えば、ユーザー入力を読み込むときに問題が発生する可能性があります。

動的割り当ての使用が必要な場合は、スマート ポインタまたは「リソースの取得は初期化」というイディオムをサポートする別の型を使ってカプセル化する必要があります(標準のコンテナーはこれをサポートしています。) 例えば、スマートポインタは std::unique_ptr と std::shared_ptr

Pointers

しかし、生のポインタには動的割り当て以外にももっと一般的な使い方がありますが、ほとんどは好みの代用品が用意されています。

  1. あなたは参照セマンティクスを必要としています。 時には、ポインタを使用してオブジェクトを渡したい場合があります (それがどのように割り当てられたかに関係なく)。 しかし、ほとんどの場合、ポインタよりも参照型の方が適しています。なぜなら、参照型は特にそのために設計されているからです。 これは、上記の状況1のように、必ずしも現在のスコープを超えてオブジェクトの寿命を延長することではないことに注意してください。 以前のように、オブジェクトのコピーを渡すことに問題がなければ、参照セマンティクスは必要ありません。
  2. ポリモーフィズムが必要です。 オブジェクトへのポインタまたは参照を通じてのみ、ポリモーフィックに (つまり、オブジェクトの動的型に従って) 関数を呼び出すことができます。 もしそれが必要な動作であれば、ポインタか参照を使う必要があります。
  3. オブジェクトが省略されているときに nullptr を渡すことを許可することによって、オブジェクトがオプションであることを表現したいのでしょう。 引数であれば、デフォルト引数や関数のオーバーロードを使用するのが望ましいでしょう。 そうでなければ、std::optional (C++17 で導入 – 以前の C++ 標準では boost::optional を使用) などの、この動作をカプセル化した型を使用するのが望ましい。
  4. コンパイル時間を改善するために、コンパイル単位をデカップリングしたいと思う。 ポインターの便利な特性は、ポイントされた型の前方宣言のみを必要とすることです (実際にオブジェクトを使用するには、定義が必要です)。 これにより、コンパイル処理の一部を切り離すことができ、コンパイル時間を大幅に短縮できる可能性があります。 Pimplイディオムを参照してください。
  5. CライブラリまたはCスタイルのライブラリとのインタフェースが必要です。 このとき、生のポインタを使わざるを得ない。 一番いいのは、生ポインタは可能な限り最後の瞬間にしか解放しないようにすることです。 例えば、スマートポインタのgetメンバ関数を使えば、スマートポインタから生ポインタを得ることができます。 ライブラリが何らかの割り当てを行い、それをハンドル経由で解放することを期待する場合、ハンドルをスマートポインタでラップして、オブジェクトを適切に解放するカスタムデレッターをしばしば使用することができます。