3
class MyContainedClass {
};

class MyClass {
public:
  MyContainedClass * getElement() {
    // ...
    std::list<MyContainedClass>::iterator it = ... // retrieve somehow
    return &(*it);
  }
  // other methods
private:
  std::list<MyContainedClass> m_contained;
};

msdn はstd::list、削除または挿入時に要素の再配置を実行すべきではないと言っていますが、リスト要素へのポインターを返すのは適切で一般的な方法ですか?

deletePS:ポインタのコレクション(およびデストラクタの要素が必要)、共有ポインタのコレクション(私は好きではない)などを使用できることを知っています.

4

9 に答える 9

1

簡単な方法

@cos、あなたが示した例では、C ++でこのシステムを作成する最も簡単な方法は、参照カウントに問題がないことだと思います。ルートドキュメントが破棄される前に、プログラムフローがコレクション内のオブジェクト(段落)への直接参照を保持するオブジェクト(ビュー)を最初に破棄することを確認するだけです。

タフウェイ

ただし、参照追跡によって存続期間を制御する場合は、段落オブジェクトがルートDocumentオブジェクトへの逆参照を保持するように、階層のより深い位置に参照を保持する必要があります。これにより、最後の段落オブジェクトが破棄された場合にのみ、Documentオブジェクトが保持されます。破壊されます。

さらに、Viewsクラス内で使用される場合、および他のクラスに渡される場合の段落参照も、参照カウントインターフェイスとして渡される必要があります。

タフネス

これは、最初にリストした単純なスキームと比較して、オーバーヘッドが大きすぎます。これにより、あらゆる種類のオブジェクトカウントのオーバーヘッドが回避され、さらに重要なことに、プログラムを継承する誰かが、システムを横断する参照依存関係スレッドトラップにトラップされません。

代替プラットフォーム

この種のツールは、.NETやJavaなどのこのスタイルのプログラミングをサポートおよび促進するプラットフォームで実行する方が簡単な場合があります。

あなたはまだ記憶について心配する必要があります

このようなプラットフォームを使用する場合でも、オブジェクトが適切な方法で参照解除されるようにする必要があります。他の優れた参考文献は、瞬く間にあなたの記憶を食い尽くす可能性があります。ご覧のとおり、参照カウントは優れたプログラミング手法の万能薬ではありませんが、システム全体を適用するとプログラマーの作業が大幅に容易になる多くのエラーチェックとクリーンアップを回避するのに役立ちます。

おすすめ

とはいえ、すべての参照カウントの疑問を引き起こした元の質問に戻ると、コレクションからオブジェクトを直接公開しても大丈夫ですか?

プログラムのすべてのクラス/すべての部分が本当に相互に依存しているプログラムは存在できません。いいえ、それは不可能です。プログラムは、クラス/モジュールがどのように相互作用するかを示す実行中のマニフェストだからです。理想的な設計では、依存関係を最小限に抑えることしかできず、依存関係を完全に削除することはできません。

ですから、私の意見では、そうです、コレクションのオブジェクトへの参照を、それらを操作する必要のある他のオブジェクトに公開することは悪い習慣ではありません。

  1. 最小限の相互依存性を確保するために、プログラムのいくつかのクラス/部分のみがそのような参照を取得できることを確認してください。

  2. 渡される参照/ポインタが具象オブジェクトではなくインターフェースであることを確認して、具象クラス間の相互依存を回避します。

  3. 参照がプログラムの奥深くまで渡されないようにしてください。

  4. これらの参照を満たす実際のオブジェクトをクリーンアップする前に、プログラムロジックが依存オブジェクトの破棄を処理することを確認してください。

于 2008-09-23T06:52:01.087 に答える
1

これをカプセル化する使用法はわかりませんが、それは私だけかもしれません。いずれにせよ、ポインターの代わりに参照を返すことは、私にとってより理にかなっています。

于 2008-09-20T15:16:29.103 に答える
1

一般的な方法で、「含まれるクラス」が本当に「MyClass」に含まれている場合、MyClass は部外者がそのプライベート コンテンツに触れることを許可してはなりません。

そのため、MyClass は、含まれているクラス オブジェクトを操作するためのメソッドを提供する必要があり、それらへのポインターを返す必要はありません。したがって、たとえば、「ここに含まれているオブジェクトの値をインクリメントする」などのメソッドは、「ここに含まれているオブジェクトの値をインクリメントします。好きなように操作してください」。

于 2008-09-20T15:20:02.233 に答える
1

場合によります...

それは、クラスをどの程度カプセル化するか、および何を非表示または表示するかによって異なります。

私が見るコードは私には問題ないようです。std::list のデータとイテレータが別のデータ/イテレータの変更/削除の場合に無効にされないという事実については正しいです。

ここで、ポインターを返すと、 std::list を内部コンテナーとして使用しているという事実が隠され、ユーザーがそのリストをナビゲートできなくなります。イテレータを返すと、クラスのユーザーはこのリストをより自由にナビゲートできますが、STL コンテナーにアクセスしていることを「認識」します。

それはあなたの選択です。

== std::list<>.end() の場合、このコードで問題が発生することに注意してください。ただし、すでにそのことを知っていると思いますが、これはこの議論の主題ではありません。

それでも、以下に要約する代替手段があります。

を使用constすると役立ちます...

非 const ポインターを返すという事実により、オブジェクトのユーザーは、オブジェクトに通知せずに、自分が手に入れることができる MyContainedClass をサイレントに変更できます。

代わりに、またはポインターを返す代わりに、const ポインターを返し (メソッドの末尾に const を付けて)、ユーザーが承認したアクセサー (一種のsetElement?) を使用せずにリスト内のデータを変更できないようにすることができます。

  const MyContainedClass * getElement() const {
    // ...
    std::list<MyContainedClass>::const_iterator it = ... // retrieve somehow
    return &(*it);
  }

これにより、カプセル化がいくらか増加します。

参考書はどうですか?

メソッドが失敗しない (つまり、常に有効なポインターを返す) 場合は、ポインターの代わりに参照を返すことを検討する必要があります。何かのようなもの:

  const MyContainedClass & getElement() const {
    // ...
    std::list<MyContainedClass>::const_iterator it = ... // retrieve somehow
    return *it;
  }

ただし、これはカプセル化とは関係ありません.. :-p

イテレータを使用していますか?

ポインタの代わりにイテレータを返さないのはなぜですか? リストを上下にナビゲートしても問題ない場合は、イテレータがポインタよりも優れており、ほとんど同じように使用されます。

ユーザーがデータを変更するのを避けたい場合は、反復子を const_iterator にします。

  std::list<MyContainedClass>::const_iterator getElement() const {
    // ...
    std::list<MyContainedClass>::const_iterator it = ... // retrieve somehow
    return it;
  }

良い面は、ユーザーがリストをナビゲートできることです。悪い面は、ユーザーがそれが std::list であることを知っていることです。

于 2008-09-20T15:23:04.970 に答える
1

Scott Meyers は著書『Effective STL: 50 Specific Ways to Improvement Your Use of the Standard Template Library』で、コンテナをカプセル化しようとする価値はないと述べています。

于 2008-09-20T15:24:31.847 に答える
1

あなたが本当に欲しいものについてよくMyClass考えてください。一部のプログラマーは、標準の STL コレクションで満たされる以上の特定のニーズがあるかどうかに関係なく、コレクションのラッパーを習慣として作成していることに気付きました。それがあなたの状況なら、それで終わりtypedef std::list<MyContainedClass> MyClassです。

に実装する予定の操作がある場合MyClassカプセル化の成功は、基になるリストへのアクセスを提供する方法よりも、提供するインターフェイスに依存します。

悪意はありませんが...提供した情報が限られているため、クライアントコードに必要な操作を実装する方法がわからないため、内部データを公開しているような匂いMyClassがします...またはおそらく、クライアント コードでどのような操作が必要になるかはまだわからないためです。これは、低レベル コードを必要とする高レベル コードの前に低レベル コードを記述しようとする場合の典型的な問題です。どのデータを操作するかはわかっていますが、そのデータで何をするかはまだ正確にはわかっていません。そのため、生データを一番上まで公開するクラス構造を作成します。ここで戦略を再考することをお勧めします。


@コス:

もちろん、カプセル化のためだけでなく MyContainedClass をカプセル化しています。より具体的な例を見てみましょう:

あなたの例は、コンテナーが何に使用されるかを知る前にコンテナーを作成しているという私の恐れを和らげるにはほとんど役に立ちません。あなたの例のコンテナ ラッパー - Document- には合計 3NewParagraph()のメソッドがDeleteParagraph()ありGetParagraph()ます。クライアントが実装での使用を認識する必要がないという意味で std::list をカプセル化します...しかし現実的には、それはファサードにすぎません-リストに格納されているオブジェクトへの生のポインターをクライアントに提供しているため、 client は依然として暗黙的に実装に関連付けられています。std::liststd::listDocument

オブジェクト (ポインターではない) をコンテナーに配置すると、それらは自動的に破棄されます (これは良いことです)。

良いか悪いかは、システムのニーズによって異なります。この実装が意味することは単純です。ドキュメントはParagraphs を所有し、ドキュメントから aParagraphが削除されると、それへのポインタはすぐに無効になります。つまり、次のようなものを実装するときは非常に注意する必要があります。

段落のコレクションを使用する以外のオブジェクトですが、それらを所有していません。

今、あなたは問題を抱えています。オブジェクト には、 が所有するオブジェクトParagraphSelectionDialogへのポインタのリストがあります。これら 2 つのオブジェクトの調整に注意しないと、または別のクライアントが!のインスタンスが保持するポインタの一部またはすべてを無効にする可能性があります。これをキャッチする簡単な方法はありません - 有効なへのポインタは deallocated へのポインタと同じように見え、最終的には有効な (しかし異なる)インスタンスを指すことさえあるかもしれません! クライアントはこれらのポインターを保持および逆参照することが許可されているため、オブジェクトの所有権を保持している場合でも、パブリック メソッドからポインターが返されるとすぐに制御を失います。ParagraphDocumentDocumentDocumentParagraphSelectionDialogParagraphParagraphParagraphDocumentParagraph

これは悪いです。不完全で表面的なカプセル化、漏れやすい抽象化になってしまい、いくつかの点で、抽象化がまったくないよりも悪いことになります。実装を非表示にしているため、クライアントはインターフェイスが指すオブジェクトの有効期間を認識できません。std::listほとんどの操作は、変更しない項目への参照を無効にしないため、ほとんどの場合は幸運に恵まれるでしょう。そして、すべてがうまくいくでしょう...間違ったものParagraphが削除されるまで、そして、そのポインターを少し長く保持しすぎたクライアントを探して、コールスタックをトレースするタスクに行き詰まることに気づきます。

修正は非常に簡単です。値またはオブジェクトを、必要な期間保存し、使用前に検証することができます。Documentそれは、使用可能な参照と引き換えに に渡す必要がある序数または ID 値のような単純なもの、または参照カウント スマート ポインターまたはウィーク ポインターのような複雑なものである可能性があります...それは本当にあなたの特定のニーズに依存しますクライアント。最初にクライアント コードを指定してDocumentから、サービスを記述します。

于 2008-09-20T17:20:51.537 に答える
0

より大きな問題は、コレクションのタイプを隠していることだと思います。そのため、要素を移動しないコレクションを使用しても、将来気が変わる可能性があります。外部からは見えないので、これを行うのはお勧めできません。

于 2008-09-20T15:02:27.767 に答える
0

STL は、カスタム カプセル化よりも将来のプログラマーになじみがあるため、可能であればこれを行うことは避けてください。STL は十分にレビューされ、文書化されていますが、アプリの有効期間の後半に発生することを考えていなかったエッジ ケースがあります。

さらに、ほとんどのコンテナーは、begin end push などの類似した操作をサポートしています。そのため、コンテナーを変更する必要がある場合、コード内のコンテナーの種類を変更するのは非常に簡単です。たとえば、deque するベクターまたは hash_map にマップするなどです。

もっと深い理由でこれをやりたいと仮定すると、これを行う正しい方法は、実装をリストするすべてのメソッドとイテレータ クラスを実装することです。変更が必要ない場合は、通話をメンバー リストの通話に転送します。何か特別なことをする必要がある場合は、変更して転送するか、いくつかのカスタム アクションを実行します (最初にこれを行うことにした理由)。

STl クラスが継承されるように設計されていれば簡単ですが、効率のためにそうしないことにしました。これに関する詳細については、「STL クラスから継承」を Google で検索してください。

于 2008-09-20T16:37:15.307 に答える
0

std::list は、リストに何かを追加または削除するときに、イテレータ、ポインター、または参照を無効にしないため (明らかに、削除されるアイテムを指すものは別として)、この方法でリストを使用しても壊れません。

他の人が指摘したように、このクラスのプライベート ビットへの直接アクセスを配布したくない場合があります。したがって、関数を次のように変更します。

  const MyContainedClass * getElement() const {
    // ...
    std::list<MyContainedClass>::const_iterator it = ... // retrieve somehow
    return &(*it);
  }

または、常に有効な MyContainedClass オブジェクトを返す場合は、使用できます

    const MyContainedClass& getElement() const {
    // ...
    std::list<MyContainedClass>::const_iterator it = ... // retrieve somehow
    return *it;
  }

呼び出しコードが NULL ポインターに対処する必要がないようにします。

于 2008-09-20T15:32:09.893 に答える