web-dev-qa-db-ja.com

C ++でオブジェクトを返す

クラスからオブジェクトを返すとき、メモリを解放する適切なタイミングはいつですか?

例、

class AnimalLister 
{
  public:
  Animal* getNewAnimal() 
  {
    Animal* animal1 = new Animal();
    return animal1;
  }
}

Animal Listerのインスタンスを作成し、そこから動物の参照を取得した場合、どこで削除する必要がありますか?

int main() {
  AnimalLister al;
  Animal *a1, *a2;
  a1 = al.getNewAnimal();
  a2 = al.getNewAnimal();
}

ここでの問題は、AnimalListerに作成された動物のリストを追跡する方法がないため、作成されたオブジェクトを削除する方法を持つようにそのようなコードのロジックを変更するにはどうすればよいですか。

34
bibstha

使用法に応じて、ここで使用できるオプションがいくつかあります。

  1. 動物を作成するたびにコピーを作成します。

    class AnimalLister 
    {
    public:
      Animal getNewAnimal() 
      {
        return Animal();
      }
    };
    
    int main() {
      AnimalLister al;
      Animal a1 = al.getNewAnimal();
      Animal a2 = al.getNewAnimal();
    }
    

    長所:

    • わかりやすい。
    • 追加のライブラリやサポートコードは必要ありません。

    短所:

    • 正常に動作するコピーコンストラクターを使用するには、Animalが必要です。
    • Animalが大きくて複雑な場合は、多くのコピーが必要になる可能性がありますが、 戻り値の最適化 は多くの状況でそれを軽減できます。
    • Animalから派生したサブクラスを返す予定がある場合は機能しません スライス プレーンなAnimalになり、余分なデータがすべて失われますサブクラス。
  2. shared_ptr<Animal>を返します:

    class AnimalLister 
    {
    public:
      shared_ptr<Animal> getNewAnimal() 
      {
        return new Animal();
      }
    };
    
    int main() {
      AnimalLister al;
      shared_ptr<Animal> a1 = al.getNewAnimal();
      shared_ptr<Animal> a2 = al.getNewAnimal();
    }
    

    長所:

    • オブジェクト階層で機能します(オブジェクトのスライスはありません)。
    • 大きなオブジェクトをコピーする必要はありません。
    • コピーコンストラクターを定義するためにAnimalは必要ありません。

    短所:

    • BoostまたはTR1ライブラリ、あるいは別のスマートポインタの実装が必要です。
  3. Animal内のすべてのAnimalLister割り当てを追跡します

    class AnimalLister 
    {
      vector<Animal *> Animals;
    
    public:
      Animal *getNewAnimal() 
      {
        Animals.Push_back(NULL);
        Animals.back() = new Animal();
        return Animals.back();
      }
    
      ~AnimalLister()
      {
         for(vector<Animal *>::iterator iAnimal = Animals.begin(); iAnimal != Animals.end(); ++iAnimal)
            delete *iAnimal;
      }
    };
    
    int main() {
      AnimalLister al;
      Animal *a1 = al.getNewAnimal();
      Animal *a2 = al.getNewAnimal();
    } // All the animals get deleted when al goes out of scope.
    

    長所:

    • 限られた時間に大量のAnimalsが必要で、それらすべてを一度にリリースすることを計画している状況に最適です。
    • カスタムメモリプールに簡単に適応でき、すべてのAnimalを単一のdeleteで解放します。
    • オブジェクト階層で機能します(オブジェクトのスライスはありません)。
    • 大きなオブジェクトをコピーする必要はありません。
    • コピーコンストラクターを定義するためにAnimalは必要ありません。
    • 外部ライブラリは必要ありません。

    短所:

    • 上記の実装はスレッドセーフではありません
    • 追加のサポートコードが必要
    • 前の2つのスキームよりも明確ではありません
    • AnimalListerが範囲外になると、Animalsが一緒に移動することは明らかではありません。 AnimalListerにハングアップするよりも長くAnimalsにハングアップすることはできません。
36
Eclipse

生のポインタの代わりにstd::tr1::shared_ptr(またはC++実装にTR1がない場合はboost::shared_ptr)を返すことをお勧めします。したがって、Animal*を使用する代わりに、代わりにstd::tr1::shared_ptr<Animal>を使用してください。

共有ポインタは参照追跡を処理し、参照が残っていない場合はオブジェクトを自動的に削除します。

24

最も簡単な方法は、通常のポインターの代わりにスマートポインターを返すことです。例えば:

std::auto_ptr< Animal> getNewAnimal() 
{
  std::auto_ptr< Animal > animal1( new Animal() );
  return animal1;
}

TR1またはBoostを使用できる場合は、shared_ptr <>を使用することもできます。

8
Igor Semenov

ポインタと割り当てられたメモリに関する古典的な問題の一種。それは責任についてです-AnimalListerオブジェクトによって割り当てられたメモリをクリーンアップする責任は誰にありますか。

割り当てられた各動物へのポインタをAnimalLister自体に格納し、クリーンアップすることができます。

ただし、main()にある動物へのポインタがいくつかあり、削除されたメモリを参照しています。

参照カウントソリューションが独自のソリューションをロールするよりもうまく機能すると思う理由の1つ。

8
itsmatt
  1. shared_ptr(これはうまく機能します)、
  2. 簡単なポインタを返し、クラスのユーザーに、それが今は自分の動物であり、終了したら削除する責任があることを伝えます。
  3. 動物ポインタの削除が必要であることを明確にする「freeAnimal(Animal *)」メソッドを実装します。

  4. 別の方法は、動物オブジェクトを直接返すことです。ポインタやnewの呼び出しはありません。コピーコンストラクターは、呼び出し元がヒープまたはスタックに格納できる独自の動物オブジェクトを取得するか、必要に応じてコンテナーにコピーできるようにします。

そう:

class AnimalLister 
{
Animal getAnimal() { Animal a; return a; }; // uses fast Return Value Optimisation
};

Animal myownanimal = AnimalLister.getAnimal(); // copy ctors into your Animal object

RVOは、ポインターの代わりにオブジェクトを返す方が実際には高速であることを意味します(コンパイラーは新しいオブジェクトを作成して呼び出し元のオブジェクトにコピーせず、呼び出し元のオブジェクトを直接使用するため)。

5
gbjbaanb

Scott Meyersによる徹底的な議論 で、彼は、shared_ptrまたはauto_ptrを使用するのが最善であると結論付けています。

3
amit

または、COM風のアプローチに従い、単純な参照カウントを適用することもできます。

  • オブジェクトを作成するときは、すぐに参照値1を指定します
  • 誰かがポインタのコピーを取得すると、AddRef()
  • 誰かがポインタのコピーをあきらめると、Release()

参照カウントが0に達すると、オブジェクトはそれ自体を削除します。

最終的にはshared_ptrが内部で行うことですが、何が起こっているかをより細かく制御でき、私の経験ではデバッグが容易です。 (これも非常にクロスプラットフォームです)。

私はまだshared_ptrに私の開発のチャンスをあまり与えていないので、それはあなたの目的に完全に役立つかもしれません。

2
brianb

オブジェクトが占有しているメモリを解放するときは、その特定のオブジェクトが不要になったときです。特定のケースでは、クラスAnimalListerのユーザーが、クラスAnimalの新しく割り当てられたオブジェクトへのポインターを要求しました。したがって、彼は、そのポインタ/オブジェクトが必要なくなったときにメモリを解放する責任があります。

AnimalLister lister;
Animal* a = lister.getNewAnimal();
a->sayMeow();
delete a;

私の意見では、この場合、何もオーバーエンジニアリングする必要はありません。 AnimalListerは、新しいAnimalオブジェクトを作成する単なるファクトリであり、それだけです。

2
martinsb

Joshの答えは本当に好きですが、まだリストされていないので、別のパターンを投入するかもしれないと思いました。アイデアは、クライアントコードに動物の追跡を処理するように強制することです。

class Animal
{
...
private:
  //only let the lister create or delete animals.
  Animal() { ... }
  ~Animal() { ... } 
friend class AnimalLister;
...
}

class AnimalLister 
{
  static s_count = 0;

public:
  ~AnimalLister() { ASSERT(s_count == 0); } //warn if all animals didn't get cleaned up

  Animal* NewAnimal() 
  {
    ++count;
    return new Animal();
  }

  void FreeAnimal(Animal* a)
  {
    delete a;
    --s_count;
  }
}
0
BigSandwich