C ++オブジェクトのインスタンス化


113

私はC ++を理解しようとしているCプログラマです。多くのチュートリアルでは、次のようなスニペットを使用したオブジェクトのインスタンス化を示しています。

Dog* sparky = new Dog();

これは後で行うことを意味します:

delete sparky;

それは理にかなっています。さて、動的なメモリ割り当てが不要な場合、上記の代わりに上記を使用する理由はありますか?

Dog sparky;

そして、sparkyがスコープから外れたら、デストラクタが呼び出されるようにしますか?

ありがとう!

回答:


166

逆に、経験則として、ユーザーコードにnew / deleteが含まれないように、常にスタック割り当てを優先する必要があります。

あなたが言うように、変数がスタックで宣言されている場合、そのデストラクタはスコープ外になると自動的に呼び出されます。これは、リソースの寿命を追跡し、リークを回避するための主要なツールです。

したがって、一般に、リソースを割り当てる必要があるたびに、それが(newを呼び出すことによる)メモリ、ファイルハンドル、ソケットなどに関係なく、コンストラクタがリソースを取得し、デストラクタがリソースを解放するクラスにそれをラップします。次に、そのタイプのオブジェクトをスタック上に作成できます。これにより、リソースがスコープ外になったときに解放されることが保証されます。そうすることで、メモリリークを確実に回避するために、新しい/削除のペアをどこでも追跡する必要がなくなります。

このイディオムの最も一般的な名前はRAIIです

また、専用のRAIIオブジェクトの外側に新しいものを割り当てる必要があるというまれなケースで、結果のポインターをラップするために使用されるスマートポインタークラスを調べます。代わりに、ポインタをスマートポインタに渡します。このポインタは、たとえば参照カウントなどによってその有効期間を追跡し、最後の参照がスコープから外れたときにデストラクタを呼び出します。標準ライブラリはstd::unique_ptr、単純なスコープベースの管理のためにあり、std::shared_ptr共有所有権を実装するために参照カウントを行います。

多くのチュートリアルは、次のようなスニペットを使用したオブジェクトのインスタンス化を示しています...

だから、あなたが発見したのは、ほとんどのチュートリアルがひどいことです。;)ほとんどのチュートリアルは、必要のないときに変数を作成するためにnew / deleteを呼び出したり、割り当ての存続期間を追跡するのに苦労したりするなど、お粗末なC ++プラクティスを教えています。


生のポインタは、auto_ptrのようなセマンティクス(所有権の転送)が必要な場合に便利ですが、スローしないスワップ操作は保持し、参照カウントのオーバーヘッドは必要ありません。エッジケースかもしれませんが、便利です。
グレッグロジャース

2
これは正解ですが、スタック上にオブジェクトを作成する習慣をつけないのは、そのオブジェクトの大きさが完全に明らかではないためです。スタックオーバーフローの例外を要求しているだけです。
dviljoen 2008

3
グレッグ:確かに。しかし、あなたが言うように、エッジケース。一般に、ポインターは回避するのが最善です。しかし、彼らはその理由で言語を使用しています。それは否定できません。:) dviljoen:オブジェクトが大きい場合は、スタックに割り当てることができるRAIIオブジェクトにラップし、ヒープに割り当てられたデータへのポインタを含めます。
2008

3
@dviljoen:いいえ、違います。C ++コンパイラは、オブジェクトを不必要に膨らませません。表示される最悪の場合は、通常、最も近い4バイトの倍数に切り上げられます。通常、ポインターを含むクラスは、ポインター自体と同じだけのスペースを取るので、スタックを使用してもコストはかかりません。
2009

20

スタック上に物を置くことは、割り当てと自動解放の点で有利かもしれませんが、いくつかの欠点があります。

  1. スタックに巨大なオブジェクトを割り当てたくない場合があります。

  2. ダイナミック派遣!このコードを考えてみましょう:

#include <iostream>

class A {
public:
  virtual void f();
  virtual ~A() {}
};

class B : public A {
public:
  virtual void f();
};

void A::f() {cout << "A";}
void B::f() {cout << "B";}

int main(void) {
  A *a = new B();
  a->f();
  delete a;
  return 0;
}

これは「B」を印刷します。スタックを使用すると何が起こるか見てみましょう:

int main(void) {
  A a = B();
  a.f();
  return 0;
}

これは「A」を出力しますが、Javaや他のオブジェクト指向言語に精通している人には直感的ではないかもしれません。その理由は、Bもうインスタンスのインスタンスへのポインタがないためです。代わりに、のインスタンスBが作成されa、タイプの変数にコピーされますA

特にC ++を初めて使用する場合は、いくつかのことが直感的に理解できないことがあります。Cではポインタがあり、それだけです。あなたはそれらを使う方法を知っていて、彼らはいつも同じことをします。C ++ではこれは当てはまりません。この例でをメソッドの引数として使用するとどうなるか想像してみてください。物事はより複雑になりa、タイプAまたは(A*またはA&参照渡し)の場合でも、大きな違いが生じます。多くの組み合わせが可能であり、それらはすべて異なる動作をします。


2
-1:値のセマンティクスを理解できない人と、ポリモーフィズムが参照/ポインターを必要とするという単純な事実(オブジェクトのスライスを回避するため)は、言語に関するいかなる種類の問題も構成しません。一部の人々がその基本的なルールを学ぶことができないからといって、c ++の能力を欠点と見なすべきではありません。
underscore_d

ヘッドアップをありがとう。ポインターや参照の代わりにオブジェクトを取得するメソッドについても同様の問題がありました。オブジェクト内にポインターがあり、この対処のためにポインターがすぐに削除されました。
BoBoDev 2017年

@underscore_d同意します。この回答の最後の段落は削除する必要があります。私がこの回答を編集したからといって、同意するという意味ではありません。その間違いを読みたくありませんでした。
TamaMcGlinn 2018

@TamaMcGlinnは同意した。良い編集をありがとう。意見部分を削除しました。
UniversE 2018

13

まあ、ポインターを使用する理由は、mallocで割り当てられたCでポインターを使用する理由とまったく同じです。変数よりもオブジェクトの寿命を長くしたい場合!

回避できる場合は、新しい演算子を使用しないことを強くお勧めします。特に例外を使用する場合。一般に、コンパイラーにオブジェクトを解放させる方がはるかに安全です。


13

&のアドレス演算子をまったく知らない人々からこのアンチパターンを見てきました。ポインターを使用して関数を呼び出す必要がある場合は、常にヒープに割り当ててポインターを取得します。

void FeedTheDog(Dog* hungryDog);

Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;

Dog goodDog;
FeedTheDog(&goodDog);

あるいは:void FeedTheDog(Dog&hungryDog); 犬犬; FeedTheDog(dog);
スコットランガム

1
@ScottLanghamは本当ですが、それが私がしようとしていた点ではありませんでした。たとえば、オプションのNULLポインターを受け取る関数を呼び出している場合や、変更できない既存のAPIである場合があります。
Mark Ransom、

7

ヒープを非常に重要な不動産として扱い、非常に慎重に使用してください。基本的な経験則は、可能な限りスタックを使用し、他に方法がない場合は常にヒープを使用することです。オブジェクトをスタックに割り当てることにより、次のような多くの利点を得ることができます。

(1)。例外が発生した場合にスタックの巻き戻しを心配する必要はありません

(2)。ヒープマネージャが必要とするよりも多くの領域を割り当てることによって引き起こされるメモリの断片化について心配する必要はありません。


1
オブジェクトのサイズについては、いくつかの考慮事項があります。スタックはサイズに制限があるため、非常に大きなオブジェクトにはヒープを割り当てる必要があります。
アダム

1
@Adam Naveenはここでも正しいと思います。大きなオブジェクトをスタックに配置できる場合は、それを実行してください。あなたがおそらくできない多くの理由があります。しかし、私は彼が正しいと思います。
マッケイ2013

5

私が心配する唯一の理由は、Dogがヒープではなくスタックに割り当てられるようになったことです。したがって、犬のサイズがメガバイトの場合、問題が発生する可能性があります。

新規/削除ルートに移動する必要がある場合は、例外に注意してください。このため、オブジェクトの有効期間を管理するには、auto_ptrまたはboostスマートポインタータイプの1つを使用する必要があります。


1

スタックに割り当てることができるとき(ヒープ上で)を新しくする理由はありません(何らかの理由で小さなスタックがあり、ヒープを使用したい場合を除きます)。

ヒープに割り当てたい場合は、標準ライブラリのshared_ptr(またはそのバリアントの1つ)の使用を検討してください。shared_ptrへのすべての参照が存在しなくなったら、これで削除が処理されます。


理由はたくさんありますが、例として、私の答えを参照してください。
dangerousdave

オブジェクトが関数のスコープよりも長く存続したいと思うかもしれませんが、OPはそれが彼らがやろうとしていたことを示唆していないようです。
スコットランガム

0

オブジェクトを動的に作成することを選択する理由は他にはありませんが、他にも理由があります。動的なヒープベースのオブジェクトを使用すると、ポリモーフィズムを利用できます。


2
スタックベースのオブジェクトを多態的に操作することもできます。この点で、スタックとヒープに割り当てられたオブジェクトに違いはありません。例:void MyFunc(){Dog dog; 送り歯); } void Feed(Animal&animal){auto food = animal-> GetFavouriteFood(); PutFoodInBowl(食品); //この例では、GetFavouriteFoodは、各動物が独自の実装でオーバーライドする仮想関数にすることができます。これは、ヒープが関与しなくても多態的に機能します。
スコットランガム

-1:ポリモーフィズムには参照またはポインターのみが必要です。それは、基礎となるインスタンスのストレージ期間に完全に依存しません。
underscore_d

@underscore_d「ユニバーサルベースクラスを使用すると、コストが発生します。ポリモーフィックになるようにオブジェクトをヒープに割り当てる必要があります。」-ビャーネ・ストロヴストルップ stroustrup.com/bs_faq2.html#object
dangerousdave

笑、ストルストルプ自身が言っても気にしないが、彼が間違っているので、それは彼にとって信じられないほど恥ずかしい引用だ。これを自分でテストするのは難しくありません。DerivedA、DerivedB、およびDerivedCをスタックにインスタンス化します。スタックに割り当てられたBaseへのポインタもインスタンス化し、A / B / Cを使用してそれを配置し、多態的に使用できることを確認します。批判的思考と標準参照を主張に適用します。たとえそれらが言語の原作者によるものであってもです。ここ:stackoverflow.com/q/5894775/2757035
underscore_d

このように言えば、私には、自動保存期間を備えた、それぞれ約1000の多態性オブジェクトの2つの個別のファミリーを含むオブジェクトがあります。スタック上でこのオブジェクトをインスタンス化し、参照またはポインタを介してこれらのメンバーを参照することにより、それらに対するポリモーフィズムのすべての機能を実現します。私のプログラムでは、動的に/ヒープに割り当てられたもの(スタックに割り当てられたクラスが所有するリソースを除く)はなく、iotaによるオブジェクトの機能は変更されません。
underscore_d

-4

Visual Studioでも同じ問題が発生しました。使用する必要があります:

yourClass-> classMethod();

のではなく:

yourClass.classMethod();


3
これは質問の答えにはなりません。ヒープに割り当てられたオブジェクト(オブジェクトへのポインターを介して)とスタックに割り当てられたオブジェクトにアクセスするには、異なる構文を使用する必要があることに注意してください。
Alexey Ivanov
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.