14

Scott Meyers による効果的な C++は、第 5 章の項目 28 で、「ハンドル」(ポインター、参照、またはイテレーター) をオブジェクト内部に返さないように指示しており、それは間違いなく良い点です。

つまり、これをしないでください:

class Family
{
public:
    Mother& GetMother() const;
}

カプセル化を破棄し、プライベート オブジェクト メンバーを変更できるようにするためです。

これもしないでください:

class Family
{
public:
    const Mother& GetMother() const;
}

「ぶら下がりハンドル」につながる可能性があるためです。つまり、既に破棄されているオブジェクトのメンバーへの参照を保持することになります。

さて、私の質問は、良い代替手段はありますか? お母さんが重いと想像してみてください!ここで、参照ではなく Mother のコピーを返すと、GetMother はかなりコストのかかる操作になります。

このような場合はどのように処理しますか?

4

6 に答える 6

15

最初に、繰り返しますが、最大の問題は寿命の問題ではなく、カプセル化の問題です。

カプセル化とは、誰も内部を知らないうちに変更できないことを意味するだけでなく、カプセル化とは、クラス内での実装方法を誰も知らないことを意味するため、インターフェイスを同一に保つ限り、クラスの内部を自由に変更できます。

さて、あなたが返す参照がそうであるかどうかはconst問題ではありません:クラスMother内にオブジェクトがあるという事実を誤って公開してFamilyしまい、(より良い表現を持っていたとしても) それを取り除くことができなくなりました。クライアントはそれに依存する可能性があり、コードを変更する必要があります...

最も簡単な解決策は、値で返すことです。

class Family {
public:

    Mother mother() { return _mother; }
    void mother(Mother m) { _mother = m; }

private:
    Mother _mother;
};

次の反復では_mother、インターフェイスを壊さずに削除できるためです。

class Family {
public:

     Mother mother() { return Mother(_motherName, _motherBirthDate); }

     void mother(Mother m) {
         _motherName = m.name();
         _motherBirthDate = m.birthDate();
     }

private:
     Name _motherName;
     BirthDate _motherBirthDate;
};

インターフェイスを 1 iota 変更せずに内部を完全に改造した方法をご覧ください。簡単なピージー。

注: 明らかに、この変換は効果のみを目的としています...

明らかに、このカプセル化はいくらかのパフォーマンスを犠牲にして行われます。ここには緊張があります。カプセル化とパフォーマンスのどちらを優先するかは、ゲッターを書くたびにあなたの判断に委ねられます。

于 2012-11-01T12:33:29.017 に答える
7

考えられる解決策は、クラスの実際の設計と、「オブジェクトの内部」と見なすものによって異なります。

  1. Motherの実装の詳細にすぎず、ユーザーFamilyから完全に隠すことができますFamily
  2. Family他のパブリックオブジェクトの構成と見なされます

Family最初のケースでは、サブオブジェクトを完全にカプセル化し、関数メンバーを介してのみアクセスを提供します(Motherパブリック インターフェイスを複製する可能性があります)。

class Family
{
  std::string GetMotherName() const { return mommy.GetName(); }
  unsigned GetMotherAge() const { return mommy.GetAge(); }
  ...
private:
   Mother mommy;
   ...
};

インターフェイスが非常に大きいと退屈になるMother可能性がありますが、おそらくこれは設計上の問題です (優れたインターフェイスには 3-5-7 メンバーが必要です)。

2 番目のケースでは、オブジェクト全体を返す必要があります。2 つの問題があります。

  1. カプセル化の内訳 (エンドユーザー コードはMother定義によって異なります)
  2. 所有権の問題 (ダングリング ポインター/参照)

問題 1 に対処するには、特定のクラスではなくインターフェイスを使用し、問題 2 に対処するには、共有または弱い所有権を使用します。

class IMother
{
   virtual std::string GetName() const = 0;
   ...
};

class Mother: public IMother
{
   // Implementation of IMother and other stuff
   ...
};

class Family
{
   std::shared_ptr<IMother> GetMother() const { return mommy; }
   std::weak_ptr<IMother> GetMotherWeakPtr() const { return mommy; }

   ...
private:
   std::shared_ptr<Mother> mommy;
   ...
};
于 2012-11-01T12:30:40.550 に答える
4

読み取り専用ビューが目的で、何らかの理由でハンドルがぶら下がるのを避ける必要がある場合は、shared_ptr<const Mother>.

そうすれば、Motherオブジェクトはオブジェクトよりも長く存続できFamilyます。もちろん、どちらもそれを保存する必要がありshared_ptrます。

考慮事項の一部は、 を使用しすぎて参照ループを作成するかどうかですshared_ptr。もしそうなら、weak_ptrハンドルがぶら下がっている可能性を受け入れるだけでなく、それを避けるためにクライアントコードを書くことを検討することもできます。たとえばstd::vector::at、ベクターが破棄されると古くなる参照を返すという事実について、誰もあまり心配していません。しかし、コンテナは、それが「所有する」オブジェクトを意図的に公開するクラスの極端な例です。

于 2012-11-01T12:16:48.527 に答える
3

これは基本的な OO の原則に戻ります。

Tell objects what to do rather than doing it for them.

あなたMotherは何か役に立つことをする必要がありますか?Familyあなたのためにそれをするようにオブジェクトに依頼してください。オブジェクトClassのメソッドのパラメーターを介して、適切なインターフェイス (c++) にラップされた外部依存関係を渡してください。Family

于 2012-11-01T12:16:40.937 に答える
2

「ぶら下がりハンドル」につながる可能性があるためです。つまり、既に破棄されているオブジェクトのメンバーへの参照を保持することになります。

ユーザーは逆参照することもnull同様に愚かなこともできますが、ライフタイムが明確で明確に定義されている限り、これを行うつもりはありません。これには何も問題はありません。

于 2012-11-01T12:24:53.593 に答える
0

それは単なるセマンティクスの問題です。あなたの場合、実装の詳細でMotherなく、内部ではありません。 クラス インスタンスは、や他の多くのエンティティで参照できます。さらに、インスタンスの有効期間は有効期間と相関しない場合さえあります。FamilyMotherFamilyMotherFamily

したがって、より良い設計は、 に格納しFamily、心配することなくインターフェイスにshared_ptr<Mother>公開することです。Family

于 2012-11-01T12:31:04.333 に答える