私はJavaのバックグラウンドから来ていて、C++でオブジェクトを扱い始めました。しかし、私が思いついたことの1つは、人々がオブジェクトそのものではなくオブジェクトへのポインタを使用することです。
Object *myObject = new Object;
のではなく:
Object myObject;
あるいは、関数を使う代わりに、次のようにtestFunc()
としましょう。
myObject.testFunc();
我々は書く必要があります:
myObject->testFunc();
しかし、なぜ私たちがこのようにしなければならないのか理解できません。メモリアドレスに直接アクセスするので、効率と速度に関係していると思います。私は正しいですか?
動的割り当てが頻繁に見られるのは非常に残念です。これは、悪いC++プログラマが何人いるかを示しているだけです。
ある意味では、2つの質問が1つにまとめられています。 1つ目は、動的割り当て(new
を使用)をいつ使用すべきかということです。 2つ目は、いつポインタを使うべきかということです。
重要なお持ち帰りのメッセージは、あなたがすべきであるということです 常に仕事に適した道具を使ってください 。ほとんどすべての状況で、手動の動的割り当てや未加工ポインタの使用よりも適切で安全なものがあります。
あなたの質問では、オブジェクトを作成する2つの方法を説明しました。主な違いは、オブジェクトの保存期間です。ブロック内でObject myObject;
を実行すると、オブジェクトは自動保存期間で作成されます。つまり、オブジェクトは有効範囲外になると自動的に破棄されます。 new Object()
を実行すると、オブジェクトは動的な保存期間を持ちます。つまり、明示的にdelete
になるまでオブジェクトは存続します。動的記憶域期間は、必要なときにだけ使用してください。 つまり、 あなたは常に可能な場合は自動保存期間を指定してオブジェクトを作成することを好む とする必要があります) - 。
動的割り振りが必要となる可能性がある主な2つの状況:
動的割り当てを絶対に必要とする場合は、スマートポインタまたは _ raii _ を実行するその他の型(標準のコンテナのように)でそれをカプセル化する必要があります。スマートポインタは、動的に割り当てられたオブジェクトの所有権セマンティクスを提供します。たとえば、 std::unique_ptr
と std::shared_ptr
を見てください。あなたがそれらを適切に使用すれば、あなたはほとんど完全にあなた自身のメモリ管理を実行することを避けることができます( ゼロの規則 を見てください)。
しかし、動的な割り当て以外にも生のポインタのための他のより一般的な用途がありますが、ほとんどはあなたが好むべき代替手段を持っています。前と同じように、 あなたが本当にポインタを必要としない限り、常に代替案を好む 。
あなたは参照セマンティクスが必要です 。受け渡し先の関数がその特定のオブジェクト(そのコピーではない)にアクセスできるようにしたいので、ポインタを使用して(割り当てられた方法に関係なく)オブジェクトを渡したいことがあります。ただし、ほとんどの場合、ポインタより参照型をお勧めします。これは特にそれらが設計されているためです。上記の状況1のように、これが必ずしもオブジェクトの存続期間を現在の範囲を超えて拡張することではないことに注意してください。以前と同様に、オブジェクトのコピーを渡しても問題ない場合は、参照セマンティクスは不要です。
あなたは多態性が必要です 。関数を呼び出すことができるのは、ポインタまたはオブジェクトへの参照を通じてのみ、多相的に(つまり、オブジェクトの動的型に応じて)関数を呼び出すことです。それがあなたが必要とする振る舞いなら、あなたはポインタか参照を使う必要があります。また、参照が優先されるべきです。
オブジェクトがオプションであることを表現したい オブジェクトが省略されているときにnullptr
を渡すことを許可する。それが引数であれば、デフォルトの引数か関数のオーバーロードを使うほうがいいでしょう。それ以外の場合は、std::optional
(C++ 17で導入 - 以前のC++標準ではboost::optional
)のように、この動作をカプセル化する型を使用することをお勧めします。
あなたはコンパイル時間を改善するためにコンパイル単位を分離したいです 。ポインタの便利な性質は、あなたが指し示す型の前方宣言だけを必要とすることです(実際にオブジェクトを使うためには、定義が必要です)。これにより、コンパイルプロセスの一部を切り離すことができ、コンパイル時間が大幅に短縮されます。 Pimplイディオム を参照してください。
あなたはCライブラリとインターフェイスする必要があります またはCスタイルのライブラリ。この時点で、あなたは生のポインタを使うことを強いられています。あなたができる最善のことは、最後の可能な瞬間にあなたの生のポインタだけを解放させるようにすることです。スマートポインタから生のポインタを取得するには、たとえば、そのget
メンバ関数を使用します。ライブラリがハンドルを介して割り当て解除を行うことを期待しているためにライブラリに割り当てを実行する場合は、多くの場合、オブジェクトを適切に割り当て解除するカスタムデリミタを使用してハンドルをスマートポインタでラップできます。
ポインタには多くのユースケースがあります。
多態的な振る舞い 多相型の場合、スライスを避けるためにポインタ(または参照)が使用されます。
class Base { ... };
class Derived : public Base { ... };
void fun(Base b) { ... }
void gun(Base* b) { ... }
void hun(Base& b) { ... }
Derived d;
fun(d); // oops, all Derived parts silently "sliced" off
gun(&d); // OK, a Derived object IS-A Base object
hun(d); // also OK, reference also doesn't slice
セマンティクスを参照し、コピーを回避する 。非多相型では、ポインタ(または参照)は潜在的に高価なオブジェクトをコピーすることを避けます
Base b;
fun(b); // copies b, potentially expensive
gun(&b); // takes a pointer to b, no copying
hun(b); // regular syntax, behaves as a pointer
C++ 11には、高価なオブジェクトの多くのコピーを関数の引数に、および戻り値として回避できる移動セマンティクスがあることに注意してください。しかし、ポインタを使用すると、それらを確実に回避し、同じオブジェクト上で複数のポインタを使用できるようになります(オブジェクトは一度だけ移動できます)。
リソース獲得 。 new
演算子を使用してリソースへのポインタを作成することは、現代のC++では アンチパターン です。特殊なリソースクラス(標準コンテナの1つ)または スマートポインタ (std::unique_ptr<>
またはstd::shared_ptr<>
)を使用してください。検討してください:
{
auto b = new Base;
... // oops, if an exception is thrown, destructor not called!
delete b;
}
対
{
auto b = std::make_unique<Base>();
... // OK, now exception safe
}
生のポインタは「ビュー」としてのみ使用されるべきであり、直接の作成によるものであるか、または戻り値によるものであるかにかかわらず、所有権には一切関係しません。 C++からのこのQ&A FAQ も参照してください。
よりきめの細かいライフタイム制御 共有ポインタがコピーされるたびに(例えば関数の引数として)、それが指すリソースは生き続けています。通常のオブジェクト(new
によって直接作成されたものでもリソースクラス内でも作成されたものではありません)は、範囲外になると破棄されます。
前方宣言、多態性などの重要なユースケースを含め、この質問に対する優れた回答は多数ありますが、質問の「魂」の一部は答えられないと感じます。
2つの言語を比較して状況を調べましょう。
Object object1 = new Object(); //A new object is allocated by Java
Object object2 = new Object(); //Another new object is allocated by Java
object1 = object2;
//object1 now points to the object originally allocated for object2
//The object originally allocated for object1 is now "dead" - nothing points to it, so it
//will be reclaimed by the Garbage Collector.
//If either object1 or object2 is changed, the change will be reflected to the other
これと最も近いのは、
Object * object1 = new Object(); //A new object is allocated on the heap
Object * object2 = new Object(); //Another new object is allocated on the heap
delete 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 STACK
Object object2; //Another new object is allocated on the STACK
object1 = 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...
これを考える最善の方法は、(多かれ少なかれ)Javaが(暗黙のうちに)オブジェクトへのポインタを処理するのに対し、C++はオブジェクトへのポインタまたはオブジェクト自体を処理することができるということです。 - たとえば、Javaの「プリミティブ」型を宣言した場合、それらはコピーされる実際の値であり、ポインタではありません。
int object1; //An integer is allocated on the stack.
int object2; //Another integer is allocated on the stack.
object1 = object2; //The value of object2 is copied to object1.
とは言っても、ポインタを使用することが必ずしも物事を処理するための正しい方法でも間違った方法でもありません。しかしながら、他の答えはそれを満足にカバーしています。しかし一般的な考え方は、C++では、オブジェクトの存続期間、およびそれらがどこに存在するかについて、はるかに制御できるということです。
注意してください - Object * object = new Object()
構文は、実際には一般的なJava(あるいはC#の場合)のセマンティクスに最も近いものです。
ポインタを使用するもう1つの正当な理由は 前方宣言 にあります。十分に大きいプロジェクトでは、彼らは本当にコンパイル時間をスピードアップすることができます。
誇大宣伝とは対照的に、JavaはC++のようなものではありません。 Java hype machineは、JavaにはC++のような構文があるので、言語は似ていると信じてほしいと思います。真実から遠いことはありません。この誤情報は、JavaプログラマーがC++に行き、コードの意味を理解せずにJavaのような構文を使用する理由の一部です。
しかし、なぜ私たちがこのようにしなければならないのか理解できません。 メモリアドレスに直接アクセスするので、は効率とスピードに関係していると思います。私は正しいですか?
それどころか、実際には。 ヒープはスタックよりはるかに遅い スタックはヒープに比べて非常に単純なので。自動ストレージ変数(別名スタック変数)は、スコープから外れるとデストラクタが呼び出されます。例えば:
{
std::string s;
}
// s is destroyed here
一方、動的に割り当てられたポインタを使用する場合は、そのデストラクタを手動で呼び出す必要があります。 delete
はこのデストラクタをあなたのために呼び出します。
{
std::string* s = new std::string;
}
delete s; // destructor called
これは、C#およびJavaで一般的なnew
構文とは関係ありません。それらはまったく異なる目的に使用されます。
1.配列のサイズを事前に知っておく必要はありません
多くのC++プログラマが最初に直面する問題の1つは、ユーザーからの任意の入力を受け付けるときには、スタック変数に固定サイズしか割り当てられないことです。配列のサイズも変更できません。例えば:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
もちろん、代わりにstd::string
を使用した場合、std::string
は内部的にサイズを変更するので問題にならないはずです。しかし、本質的にこの問題の解決策は動的割り当てです。ユーザーの入力に基づいて動的メモリを割り当てることができます。次に例を示します。
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
サイドノート:多くの初心者が犯す1つの間違いは可変長配列の使い方です。これはGNU拡張子であり、またClang にも含まれています。これらはGCCの多くの拡張子を反映しているためです。そのため、次の
int arr[n]
は信頼しないでください。
ヒープはスタックよりはるかに大きいため、スタックには制限がありますが、必要なだけのメモリを任意に割り当て/再割り当てすることができます。
2.配列はポインタではありません
これはどのようにあなたが求める利益ですか?配列とポインタの背後にある混乱/神話を理解すれば、答えは明らかになります。一般的には、それらは同じであると見なされますが、そうではありません。この神話は、ポインタは配列と同じように添字を付けることができ、配列のために関数宣言の最上位レベルでポインタに崩壊するという事実から来ています。しかし、一旦配列がポインタに崩壊すると、ポインタはそのsizeof
情報を失います。そのため、sizeof(pointer)
はポインタのサイズをバイト単位で指定します。64ビットシステムでは通常8バイトです。
配列に代入することはできません。初期化するだけです。例えば:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
その一方で、あなたはあなたが望むことなら何でもポインタで行うことができます。残念ながら、ポインタと配列の違いはJavaとC#では手で振られているので、初心者はその違いを理解していません。
3.多態性
JavaとC#には、たとえばas
キーワードを使用して、オブジェクトを別のオブジェクトとして扱うことを可能にする機能があります。したがって、誰かがEntity
オブジェクトをPlayer
オブジェクトとして扱いたい場合は、Player player = Entity as Player;
を実行できます。これは、特定の型にのみ適用される同種のコンテナ上で関数を呼び出す場合に非常に便利です。機能は、以下のようにして実現できます。
std::vector<Base*> vector;
vector.Push_back(&square);
vector.Push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
もしTrianglesだけがRotate関数を持っていたとしても、そのクラスのすべてのオブジェクトに対してそれを呼び出そうとしたのであれば、コンパイラエラーになるでしょう。 dynamic_cast
を使用すると、as
キーワードをシミュレートできます。明確にするために、キャストが失敗した場合、無効なポインタを返します。そのため!test
は、基本的にtest
がNULLまたは無効なポインタであるかどうかをチェックするための省略形です。これはキャストが失敗したことを意味します。
動的割り振りができることをすべて理解した後で、なぜ動的割り振りを常に使用しないのはなぜだろうかと疑問に思いますか。私はすでにあなたに1つの理由を話しました、ヒープが遅いです。そして、あなたがそのようなメモリを全部必要としないのなら、あなたはそれを悪用してはいけません。それで、ここでいくつかの不利な点が順不同である:
エラーが発生しやすいです。手動のメモリ割り当ては危険であり、あなたはリークする傾向があります。デバッガやvalgrind
(メモリリークツール)の使用に習熟していない場合は、頭から髪の毛を引き出すことができます。幸いなことに、RAIIの慣用句と賢いポインタによってこれは少し軽減されますが、The Rule Of ThreeやThe Rule Of Fiveなどの慣例に精通している必要があります。それは取り込むべきたくさんの情報です、そして知らないか気にしない初心者はこの罠に落ちるでしょう。
それは必要ない。どこでもnew
キーワードを使用するのが慣用的であるJavaやC#とは異なり、C++では、必要な場合にのみ使用してください。一般的な言い方をすれば、ハンマーを持っていればすべてが釘のように見えます。 C++で始める初心者はポインタを怖がっていて習慣によってスタック変数を使うことを学びますが、JavaやC#プログラマは 開始 それを理解せずにポインタを使用することによって!それは文字通り間違った足で踏み出すことです。構文は1つのことであり、言語を学ぶことは別なので、あなたはあなたが知っているすべてのものを放棄しなければなりません。
1.(N)RVO - 別名、(名前付き)戻り値の最適化
多くのコンパイラが行う1つの最適化は、次のようなものです。 エリシション そして 戻り値の最適化。これらのことは、多くの要素を含むベクトルのように非常に大きいオブジェクトに役立つ不要なコピーを不要にすることができます。通常、一般的なやり方はへのポインタを使うことです。 所有権の譲渡 ラージオブジェクトを 動く 彼らの周りこれはの開始につながっています 意味を移動する そして スマートポインタ。
あなたがポインタを使っているなら、(N)RVOは ではない 起こる。最適化が心配な場合は、ポインタを返すか渡すよりも、(N)RVOを利用するほうが有益であり、エラーが発生しにくくなります。関数の呼び出し元が動的に割り当てられたオブジェクトのdelete
ingなどを担当している場合、エラーリークが発生する可能性があります。ホットポテトのようにポインタが渡されている場合は、オブジェクトの所有権を追跡するのが難しい場合があります。スタック変数を使用するのは、単純で優れているからです。
C++では、オブジェクトを渡す方法として、ポインター、参照、値の3つの方法があります。 Javaは、後者を制限します(唯一の例外は、int、booleanなどのプリミティブ型です)。奇妙なおもちゃのようにC++を使用したくない場合は、これら3つの方法の違いを理解することをお勧めします。
Javaは、「誰がいつこれを破壊すべきか」などの問題はないと装います。答えは、「ガベージコレクター、素晴らしくてひどい」です。それでも、メモリリークに対する100%の保護を提供することはできません(はい、 Java canメモリのリーク )。実際、GCは誤った安全性の感覚を与えます。 SUVが大きいほど、避難者への道のりは長くなります。
C++を使用すると、オブジェクトのライフサイクル管理を直接行うことができます。まあ、それを処理する手段があります( スマートポインター ファミリー、QtのQObjectなど) alwaysメモリ処理に留意してください。オブジェクトの破棄を気にするだけでなく、同じオブジェクトを複数回破棄しないようにする必要があります。
まだ怖くない? Ok:循環参照-人間が自分で処理します。そして、覚えておいてください。各オブジェクトを一度だけ正確に殺してください。私たちのC++ランタイムは、死体をいじり、死んだものを放っておく人を嫌います。
それでは、質問に戻りましょう。
ポインタや参照ではなく値でオブジェクトを渡す場合、オブジェクトをコピーします(オブジェクト全体、数バイトでも巨大なデータベースダンプでも、後者を回避するのに十分な賢さです)。 t?)「=」を実行するたびに。オブジェクトのメンバーにアクセスするには、「。」を使用します(ドット)。
オブジェクトをポインターで渡す場合、ほんの数バイト(32ビットシステムでは4バイト、64ビットシステムでは8バイト)、つまり-このオブジェクトのアドレスをコピーします。これをすべての人に見せるために、メンバーにアクセスするときにこの派手な「->」演算子を使用します。または、「*」と「。」の組み合わせを使用できます。
参照を使用すると、値のふりをするポインターを取得します。これはポインタですが、「。」を使用してメンバーにアクセスします。
そして、もう一度心を吹き飛ばすために:コンマで区切られたいくつかの変数を宣言するとき(手を見てください):
例:
struct MyStruct
{
int* someIntPointer, someInt; //here comes the surprise
MyStruct *somePointer;
MyStruct &someReference;
};
MyStruct s1; //we allocated an object on stack, not in heap
s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
s1.someIntPointer = &s1.someInt;
*s1.someIntPointer = 2; //now s1.someInt has value '2'
s1.somePointer = &s1;
s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
s1.somePointer->someInt = 3; //now s1.someInt has value '3'
*(s1.somePointer).someInt = 3; //same as above line
*s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'
s1.someReference.someInt = 5; //now s1.someInt has value '5'
//although someReference is not value, it's members are accessed through '.'
MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.
//OK, assume we have '=' defined in MyStruct
s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one
しかし、私たちはなぜこれをこのように使うべきなのか理解できませんか?
あなたが使用するならば、私はそれが関数本体の中でどのように働くか比較します:
Object myObject;
関数の中では、この関数が戻るとmyObject
は破壊されます。したがって、これはあなたが自分のオブジェクトを自分の関数の外に必要としない場合に便利です。このオブジェクトは現在のスレッドスタックに置かれます。
関数本体の中に書くと:
Object *myObject = new Object;
myObject
が指すObjectクラスのインスタンスは、関数が終了しても破棄されず、割り当てはヒープ上にあります。
今、あなたがJavaプログラマーであれば、2番目の例はオブジェクト割り当てがJavaの下でどのように機能するのかに近いです。この行:Object *myObject = new Object;
はJava:Object myObject = new Object();
と同等です。違いは、JavaではmyObjectはガベージコレクトされますが、c ++では解放されません。どこかで明示的に `delete myObject 'を呼び出す必要があります。それ以外の場合は、メモリリークが発生します。
C++ 11以降、shared_ptr/unique_ptrに値を格納することで、安全な動的割り当て方法new Object
を使用できます。
std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");
// since c++14
std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared");
また、オブジェクトはmap-sやvector-sのようにコンテナに格納されることが非常に多く、オブジェクトの寿命を自動的に管理します。
C++では、(ブロック内でObject object;
ステートメントを使用して)スタックに割り当てられたオブジェクトは、宣言されたスコープ内にのみ存在します。コードブロックが実行を終了すると、宣言されたオブジェクトは破棄されます。 Object* obj = new Object()
を使用してヒープ上にメモリを割り当てる場合、それらはdelete obj
を呼び出すまでヒープ内に存在し続けます。
オブジェクトを宣言/割り当てしたコードブロック内だけではなくオブジェクトを使用する場合は、ヒープ上にオブジェクトを作成します。
技術的にはメモリ割り当ての問題ですが、ここでさらに2つの実用的な側面があります。これは2つのことと関係があります。 1)スコープ、ポインタなしでオブジェクトを定義するときそれが定義されたコードブロックの後でそれにアクセスすることはもはやできません、あなたが "new"でポインタを定義するならば、あなたは同じポインタで "delete"を呼び出すまでこのメモリへのポインタを持っている場所からアクセスできます。 2)関数に引数を渡したい場合は、より効率的にするためにポインタまたは参照を渡します。 Objectを渡すとオブジェクトがコピーされます。これが大量のメモリを使用するオブジェクトである場合、これはCPUを消費する可能性があります(たとえば、データでいっぱいのベクトルをコピーする)。ポインタを渡すときに渡すのは1つのintだけです(実装によって異なりますが、ほとんどは1つのintです)。
それ以外に、「new」はある時点で解放する必要があるメモリをヒープ上に割り当てるということを理解する必要があります。 「new」を使用する必要がない場合は、「スタック上」で通常のオブジェクト定義を使用することをお勧めします。
主な質問は、オブジェクト自体ではなくポインタを使用する必要があるのはなぜですか?そして、私の答えは、(ほとんど)オブジェクトの代わりにポインタを使用しないでください、C++には references があるため、ポインターより安全であり、ポインターと同じパフォーマンスを保証します。
あなたの質問であなたが言及した別のこと:
Object *myObject = new Object;
どのように機能しますか? Object
タイプのポインターを作成し、1つのオブジェクトに合うようにメモリを割り当て、デフォルトのコンストラクターを呼び出します。しかし、実際にはあまり良くありません。メモリを動的に割り当てた場合(キーワードnew
を使用)、手動でメモリを解放する必要があります。
delete myObject;
これはデストラクタを呼び出してメモリを解放しますが、簡単に見えますが、大きなプロジェクトでは1つのスレッドがメモリを解放したかどうかを検出するのは難しいかもしれませんが、そのために 共有ポインタ を試すことができますが、これらはパフォーマンスをわずかに低下させますが、それらを使用する方がはるかに簡単です。
そして、いくつかの紹介が終わり、質問に戻ります。
関数間でデータを転送する際に、オブジェクトの代わりにポインターを使用してパフォーマンスを向上させることができます。
std::string
(オブジェクトでもあります)があり、大きなXMLなどの大量のデータが含まれているので、解析する必要がありますが、そのためにvoid foo(...)
関数があります。さまざまな方法で宣言されました:
void foo(std::string xml);
この場合、変数から関数スタックにすべてのデータをコピーします。時間がかかるため、パフォーマンスが低下します。void foo(std::string* xml);
この場合、size_t
変数を渡すのと同じ速度でオブジェクトにポインターを渡しますが、NULL
ポインターまたは無効なポインターを渡すことができるため、この宣言にはエラーが発生しやすくなります。参照を持たないため、通常C
で使用されるポインター。void foo(std::string& xml);
ここでは、参照を渡します。基本的にはポインターを渡すのと同じですが、コンパイラーは何らかの処理を行い、無効な参照を渡すことはできません(実際、無効な参照でシチュエーションを作成することは可能ですが、コンパイラーをだます)。void foo(const std::string* xml);
これは2番目と同じです。ポインタ値のみを変更することはできません。void foo(const std::string& xml);
これは3番目と同じですが、オブジェクトの値は変更できません。さらに言及したいのは、これらの5つの方法を使用して、選択した割り当て方法に関係なくデータを渡すことができることです(new
またはregular)。
言及すべきもう1つのことは、regularの方法でオブジェクトを作成するとき、スタックにメモリを割り当てますが、new
でオブジェクトを作成する間、ヒープを割り当てます。スタックを割り当てる方がはるかに高速ですが、本当に大きなデータの配列にはやや小さいので、スタックオーバーフローが発生する可能性があるため、大きなオブジェクトが必要な場合はヒープを使用する必要がありますが、通常、この問題は STLコンテナ そしてstd::string
もコンテナであることを忘れないでください。
反対するためにポインタを使用することには多くの利点があります -
class A
を含むclass B
があるとしましょうclass B
の外でclass A
の関数を呼び出したい場合、このクラスへのポインタを取得するだけで、何でもできますあなたが望むと、それはあなたのclass B
のclass A
のコンテキストも変更します
ただし、動的オブジェクトには注意してください
これは長い間議論されてきましたが、Javaではすべてがポインタです。スタックとヒープの割り当ては区別されません(すべてのオブジェクトはヒープ上に割り当てられます)ので、ポインタを使用していることに気付かないでしょう。 C++では、メモリ要件に応じて、2つを混在させることができます。 C++では、パフォーマンスとメモリ使用量がより決定的になります(当たり前)。
Object *myObject = new Object;
これを行うことは メモリリークを避けるために明示的に削除されなければならない(ヒープ上の)Objectへの参照を作成するでしょう 。
Object myObject;
これを行うと、(スタック上の) automatic タイプのオブジェクト(myObject)が作成され、オブジェクト(myObject)がスコープ外になると自動的に削除されます。
ポインタは、オブジェクトのメモリ位置を直接参照します。 Javaにはこのようなものは何もありません。 Javaにはハッシュテーブルを通してオブジェクトの位置を参照する参照があります。これらの参照では、Javaのポインタ演算のようなことはできません。
あなたの質問に答えるために、それはあなたの好みです。私はJavaのような構文を使うのが好きです。
ポインターを使用する理由の1つは、C関数とインターフェースをとることです。もう一つの理由はメモリを節約することです。たとえば、大量のデータを含み、プロセッサを集中的に使用するコピーコンストラクタを持つオブジェクトを関数に渡すのではなく、単にオブジェクトへのポインタを渡すだけでよく、特にループ内にいる場合はメモリと速度を節約できます。その場合は、Cスタイルの配列を使用していない限り、参照はより適切です。
ポインタの重要な使用例を1つ紹介します。基本クラスに何らかのオブジェクトを格納しているときに、多相になる可能性があります。
Class Base1 {
};
Class Derived1 : public Base1 {
};
Class Base2 {
Base *bObj;
virtual void createMemerObects() = 0;
};
Class Derived2 {
virtual void createMemerObects() {
bObj = new Derived1();
}
};
したがって、この場合はbObjを直接オブジェクトとして宣言することはできません。ポインタが必要です。
メモリ使用率が最も重要な分野では、ポインタが役立ちます。たとえば、再帰ルーチンを使用して何千ものノードが生成され、後でそれらを使用してゲーム内の次善の動きを評価するミニスマートアルゴリズムを検討します。非ポインター変数は、再帰呼び出しが値を返すまでスペースを占有し続けます。
ポインタ付き 、
直接メモリと話すことができます。
ポインタを操作することによって、プログラムのメモリリークを防ぐことができます。