34

クラスからオブジェクトを返すとき、いつメモリを解放するのが適切ですか?

例、

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

Animal Lister のインスタンスを作成し、そこから Animal 参照を取得した場合、どこで削除すればよいですか?

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

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

4

9 に答える 9

36

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

  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.
    

    長所:

    • Animal限られた時間内に多数の が必要で、それらを一度にリリースする予定がある場合に最適です。
    • カスタム メモリ プールに簡単に適応できAnimal、単一の ですべての を解放できdeleteます。
    • オブジェクト階層で動作します (オブジェクトのスライスなし)。
    • 大きなオブジェクトをコピーする必要はありません。
    • Animalコピー コンストラクターを定義する必要はありません。
    • 外部ライブラリは必要ありません。

    短所:

    • 上記の実装はスレッドセーフではありません
    • 追加のサポート コードが必要です
    • 前の 2 つのスキームよりも明確でない
    • AnimalLister が範囲外になると、AnimalLister が一緒に連れて行かれることは明らかではありません。AnimalLister にぶら下がっている以上、Animals にぶら下がっていることはできません。
于 2009-01-06T21:29:31.040 に答える
24

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

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

于 2008-10-15T11:34:21.840 に答える
8

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

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

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

独自のソリューションを展開するよりも、参照カウント ソリューションの方がうまく機能すると私が考える理由の 1 つです。

于 2008-10-15T11:41:49.330 に答える
8

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

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

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

于 2008-10-15T11:34:33.010 に答える
5
  1. shared_ptr (うまく機能します)、
  2. 単純なポインターを返し、クラスのユーザーに、それが現在自分の動物であり、終了したら削除する責任があることを伝えます。
  3. 動物ポインターの削除が必要であることを明らかにする「freeAnimal(Animal*)」メソッドを実装します。

  4. もう 1 つの方法は、ポインターや 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 は、ポインターの代わりにオブジェクトを返す方が実際には高速であることを意味します (コンパイラーは新しいオブジェクトを作成して呼び出し元のオブジェクトにコピーするのではなく、呼び出し元のオブジェクトを直接使用するため)。

于 2008-10-15T12:07:38.387 に答える
3

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

于 2009-06-11T11:44:04.997 に答える
2

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

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

私の意見では、この場合、過度に設計する必要はありません。AnimalLister は、新しい Animal オブジェクトを作成する単なるファクトリであり、それだけです。

于 2008-10-15T12:36:50.013 に答える
2

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

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

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

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

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

于 2008-10-15T11:42:03.447 に答える
0

I really like Josh's answer, but I thought I might throw in another pattern because it hasn't been listed yet. The idea is just force the client code to deal with keeping track of the animals.

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;
  }
}
于 2009-02-07T22:12:31.133 に答える