0

ListC++ でリストを処理するためのかわいいジェネリック (つまりテンプレート)クラスを作成しました。その理由は、このstd::listクラスが日常的に使用するにはひどく見苦しく、常にリストを使用しているため、新しいリストが必要だったからです。主な改善点は、私のクラスでは、 を使用[]してそこからアイテムを取得できることです。また、まだ実装されていないのは、IComparer物事を分類するためのシステムです。

このListクラスはOBJLoader、Wavefront .obj ファイルをロードしてメッシュに変換するクラスで使用しています。OBJLoader次の「タイプ」へのポインタのリストが含まれています: 3D 位置、3D 法線、UV テクスチャ座標、頂点、面、およびメッシュ。頂点リストには、すべての 3D 位置、3D 法線、および UV テクスチャ座標リストの一部のオブジェクトにリンクする必要があるオブジェクトがあります。面は頂点にリンクし、メッシュは面にリンクします。したがって、それらはすべて相互に接続されています。

List<Person*>簡単にするために、あるコンテキストでは、ポインターのリストがとの 2 つだけであると考えてみましょうList<Place*>Personclass には fieldが含まれList<Place*> placesVisitedPlaceclass には field が含まれますList<Person*> peopleThatVisited。したがって、構造は次のとおりです。

class Person
{
    ...
  public:
    Place* placeVisited;
    ...
};

class Place
{
    ...
  public:
    List<People*> peopleThatVisited;
};

これで、次のコードができました。

Person* psn1 = new Person();
Person* psn2 = new Person();

Place* plc1 = new Place();
Place* plc2 = new Place();
Place* plc2 = new Place();


// make some links between them here:
psn1->placesVisited.Add(plc1, plc2);
psn2->placesVisited.Add(plc2, plc3);

// add the links to the places as well
plc1->peopleThatVisited.Add(psn1);
plc2->peopleThatVisited.Add(psn1, psn2);
plc3->peopleThatVisited.Add(plc3);

// to make things worse:

List<Person*> allThePeopleAvailable;

allThePeopleAvailable.Add(psn1);
allThePeopleAvailable.Add(psn2);

List<Place*> allThePlacesAvailable;

allThePlacesAvailable.Add(plc1);
allThePlacesAvailable.Add(plc2);
allThePlacesAvailable.Add(plc3);

すべて完了。に到達するとどうなり}ますか? すべての dtor が呼び出され、2 回以上削除しようとするため、プログラムがクラッシュします。

リストの dtor は次のようになります。

~List(void)
{
    cursor = begin;
    cursorPos = 0;

    while(cursorPos &#60; capacity - 1)
    {
        cursor = cursor->next;
        cursorPos++;
        delete cursor->prev;
    }

    delete cursor;
}

どこにElemある:

struct Elem
{
  public:
    Elem* prev;
    T value;
    Elem* next;
};

そしてTジェネリックListタイプです。

Listクラスを安全に削除するには、どのような方法がありますか? 内部の要素はポインターである場合とそうでない場合があり、それらがポインターの場合、 my を削除するときに、内部の要素を削除するか、それらの周りのラッパーListだけを削除するかを指定できるようにしたいと考えています。Elem

スマート ポインターが答えになる可能性がありますが、それは を持てないことを意味しますがList<bubuType*>List<smart_pointer_to_bubuType>. これはList<bubuType*>問題ないかもしれませんが、繰り返しになりますが、a を宣言してもエラーや警告は発生せず、場合によっては、スマート ポインターが実装で問題を引き起こす可能性がありList<PSTR>ます。PSTRそれらをスマートポインターの中に入れるのは醜い仕事だと思います。したがって、私が探している解決策は、Listテンプレートの割り当て解除システムに何らかの形で関連していると思います。

何か案は?

4

9 に答える 9

11

あなたのコードを見ることさえせずに、私は言います:それを捨ててください!

C++ には、すべての C++ プログラマーによく知られている効率的なリスト クラス テンプレートがあり、コンパイラーにはバグがありません。

STL の使い方を学びます。1他の OO 言語から来た STL は奇妙に見えるかもしれませんが、その奇妙さの根底には理由があります。ステパノフが来て STL を考え出す前は不可能と考えられていた、抽象化とパフォーマンスを組み合わせた異質な美しさです。
STL を理解するのに苦労しているのはあなただけではありません。それが私たちに出くわしたとき、私たちは皆、その概念を理解し、その特殊性を学び、それがどのようにカチカチ音をたてるかを理解するのに苦労しました. STL奇妙な獣ですが、誰もが結合できないと思っていた 2 つの目標を結合することに成功したため、最初はなじみがないように見えます。

独自の連結リスト クラスを作成することは、C++ プログラマーにとって、独自の文字列クラスを作成するのに次いで 2 番目に人気のある屋内スポーツだったに違いありません。15 年前に C++ をプログラミングしていた私たちは、最近では、バグだらけで非効率的で奇妙で未知の文字列、リスト、および古いコードで腐敗している辞書クラスを取り除き、それを非常に効率的なものに置き換えることを楽しんでいます。知られバグフリー。独自のリスト クラスを (教育目的以外で) 開始することは、最悪の異端の 1 つに違いありません。

C++ でプログラミングする場合は、できるだけ早くそのボックスに含まれている最も強力なツールの 1 つに慣れてください。

1 「STL」という用語は、標準ライブラリ全体ではなく、Stepanov のライブラリに由来する C++ 標準ライブラリの一部 (さらにstd::string後付けとして STL インターフェイスが追加されたものなど) を指していることに注意してください。

于 2011-03-10T18:42:38.850 に答える
3

そのメモリの割り当てを解除する責任がある場合は、常にスマートポインタを使用してください。そのメモリを削除する責任がないことがわかっている場合を除いて、rawポインタを使用しないでください。WinAPIリターンの場合は、それらをスマートポインターにラップします。もちろん、メモリを所有していないオブジェクトのリストが必要な場合があるため、rawポインタのリストはエラーではありません。しかし、スマートポインターは完全に不可欠なツールであるため、スマートポインターを回避することは、問題の解決策ではありません。

そして、標準リストを使用してください。それが目的です。

于 2011-03-10T18:50:47.943 に答える
3

最良の答えは、オブジェクトのそれぞれの存続期間と、その存続期間を管理する責任について考える必要があるということです。

特に、人々と彼らが訪れた場所との関係において、おそらくどちらも、他の人々の生涯に責任を負うべきではありません.人々は、彼らが訪れた場所から独立して生きることができます.彼らは訪問されました。これは、人とサイトの両方の寿命が他のものとは無関係であり、保持されているポインターがリソース管理に関係なく、むしろ参照であることを示唆しているようです(C++ の意味ではありません)。

リソースの管理責任者がわかったら、それをコードにする必要がありますdelete(または、動的に割り当てる必要がある場合は、コンテナーまたは適切なスマート ポインターにリソースを保持する方が適切です)。また、削除が行われないようにする必要があります。同じ要素を参照する他のオブジェクトがそれらを終了する前に。

一日の終わりに設計の所有権が明確でない場合は、メモリ リークを引き起こす循環依存関係を作成しないように注意しながら、 shared_ptr(またはのいずれboostか) を使用するようにフォールバックできます。std繰り返しますshared_ptrが、s を正しく使用するには、戻って考え、オブジェクトの寿命について考える必要があります...

于 2011-03-10T18:42:41.683 に答える
1

行内: // ここでそれらの間にいくつかのリンクを作成します: psn1->placesVisited.Add(plc1, plc2); psn2->placesVisited.Add(plc2, plc3);

// 場所へのリンクも追加します plc1->peopleThatVisited.Add(psn1); plc2->peopleThatVisited.Add(psn1, psn2); plc3->peopleThatVisited.Add(plc3);


相互へのポインターを含むインスタンスがヒープ上にあります。1 人だけでなく、同じ Place ポインターを複数の人に追加すると、問題が発生します (メモリ内の同じオブジェクトを複数回削除する)。

STL を学習するか、shared_ptr (Boost) を使用するように指示することは良いアドバイスですが、David Rodríguez が言ったように、オブジェクトの寿命を考える必要があります。つまり、オブジェクトが相互にポインターを含まないように、このシナリオを再設計する必要があります。

例: ポインターを使用することは本当に必要ですか? - この場合、STL リストまたはベクトルが必要な場合は、shared_ptr を使用する必要がありますが、オブジェクトが相互に参照する場合、最高の shared_ptr 実装でさえそれを行いません。

場所と人物の間のこの関係が必要な場合は、クラスを設計するか、人物と場所が互いに指すのではなく、相互への参照を保持するコンテナーを使用します。RDBMS の多対多テーブルのようなものです。次に、プロセスの最後にポインターを削除するクラス/コンテナーが作成されます。このように、Places と Persons の間の関係は存在せず、コンテナー内にのみ存在します。

よろしく、 J.リベロ

于 2011-03-10T19:07:24.137 に答える
0

まずSTL(標準テンプレートライブラリ)を使うのは当然なのですが、C++を学んでいるようですので、演習としてListテンプレートなどを書いてみるといいかもしれません。

まず第一に、データ メンバー (placeVisitedおよび などpeopleThatVisited) を公開しないでください。これは、オブジェクト指向プログラミングの黄金律です。そのためには、getter メソッドと setter メソッドを使用する必要があります。

二重削除に関する問題について: 唯一の解決策は、ポインターの周りにラッパー クラスを配置することです。これにより、未解決の参照が追跡されます。boost::shared_ptrを見てください。( Boostは、巧妙に作成された素晴らしい C++ ライブラリの 1 つです)。

于 2011-03-10T18:54:29.947 に答える
0

delete は指定されたポインターのメモリの割り当てを解除するため、プログラムはクラッシュします。リストからアイテムを削除していません。少なくとも 2 つのリストに同じポインターがあるため、メモリの同じブロックで削除が複数回呼び出され、クラッシュが発生します。

于 2011-03-10T18:55:21.797 に答える
0

まず、スマート ポインターはここでの答えではありません。それらが行うことは、オブジェクトが決して削除されないことを保証することだけです (二重リンクリストには定義によりサイクルが含まれるため)。

次に、デストラクタに引数を渡して、含まれているポインタを削除するように指示する方法はありません。これは、リストの型、そのテンプレート引数の 1 つ、部分的な特殊化、またはコンストラクタへの引数のいずれかを介して行う必要があります。(後者は、非ポインターを削除しようとするのを避けるために、おそらく部分的な特殊化も必要になります。)

最後に、オブジェクトを 2 回削除しない方法は、delete を 2 回呼び出さないことです。デストラクタで何が起こっているのかよくわかりませんが、カーソルを変更することはないので、毎回同じ2つの要素を削除しています. おそらく、次の行に沿ってさらに何かが必要です。

while ( cursor not at end ) {
    Elem* next = cursor->next;
    delete cursor;
    cursor = next;
}

-- ジェームズ・カンゼ

于 2011-03-10T19:15:13.607 に答える
0

Add 関数の正確なコードとリストのデストラクタを見ないと、問題を特定するのは困難です。

ただし、コメントで述べたように、このコードの主な問題は、std::listまたはを使用しないことですstd::vector。必要なものに適合する、実証済みの効率的な実装があります。

于 2011-03-10T18:41:42.627 に答える
-1

また、通常のリストの周りに [] を簡単に実装できます。

template <class Type> 
class mystdlist : public std::list<Type> { 
public: 
    Type& operator[](int index) { 
       list<Type>::iterator iter = this.begin(); 

       for ( int i = 0; i < index; i++ ) { 
          iter++; 
       }
       return *iter; 
    }  
};  

なぜこれをやりたいのかは奇妙です、IMHO。O(1) アクセスが必要な場合は、ベクトルを使用します。

于 2011-03-10T19:01:42.763 に答える