36

これがコンパイルされる理由:

class FooBase
{
protected:
    void fooBase(void);
};

class Foo : public FooBase
{
public:
    void foo(Foo& fooBar)
    {
        fooBar.fooBase();
    }
};

しかし、これはしませんか?

class FooBase
{
protected:
    void fooBase(void);
};

class Foo : public FooBase
{
public:
    void foo(FooBase& fooBar)
    {
        fooBar.fooBase();
    }
};

一方では、C++ はそのクラスのすべてのインスタンスに対してプライベート/保護されたメンバーへのアクセスを許可しますが、他方では、サブクラスのすべてのインスタンスに対して基本クラスの保護されたメンバーへのアクセスを許可しません。これは私にはかなり矛盾しているように見えます。

VC++ と ideone.com でコンパイルをテストしましたが、どちらも最初のコード スニペットをコンパイルしますが、2 番目のコード スニペットはコンパイルしません。

4

8 に答える 8

31

が参照fooを受け取るFooBaseと、コンパイラは引数が の子孫であるかどうかがわからないFooため、そうではないと想定する必要があります。他のすべての兄弟クラスではなく、他のオブジェクトFooの継承された保護されたメンバーにアクセスできます。Foo

次のコードを検討してください。

class FooSibling: public FooBase { };

FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?

Foo::foo任意FooBaseの子孫の保護されたメンバーを呼び出すことができる場合はFooSibling、 と直接関係のないの保護されたメソッドを呼び出すことができますFoo。それは、保護されたアクセスが機能するはずの方法ではありません。

子孫として知られているオブジェクトだけでなくFoo、すべてのオブジェクトの保護されたメンバーにアクセスする必要がある場合は、 のフレンドである必要があります。FooBaseFooFooFooBase

class FooBase
{
protected:
  void fooBase(void);
  friend class Foo;
};
于 2012-07-24T13:26:27.117 に答える
21

C++ FAQは、この問題をうまく要約しています:

[あなた]は自分のポケットを選ぶことは許されていますが、父親のポケットや兄弟のポケットを選ぶことは許されていません.

于 2013-01-04T10:00:31.983 に答える
10

重要な点は、のオブジェクトprotectedのメンバーではなく、メンバーの独自のコピーへのアクセスを許可することです。これはよくある誤解です。多くの場合、派生型へのメンバーへのアクセスを一般化し、状態を付与します (自身のベースに対してのみ明示的に述べずに...)。protected

これには理由があります。他のオブジェクトが依存している不変条件を壊す可能性があるため、通常、階層の別の分岐にあるメンバーにアクセスしないでください。いくつかの大規模なデータ メンバー (保護されている) に対してコストのかかる計算を実行する型と、異なる戦略に従って結果をキャッシュする 2 つの派生型を考えてみましょう。

class base {
protected:
   LargeData data;
// ...
public:
   virtual int result() const;      // expensive calculation
   virtual void modify();           // modifies data
};
class cache_on_read : base {
private:
   mutable bool cached;
   mutable int cache_value;
// ...
   virtual int result() const {
       if (cached) return cache_value;
       cache_value = base::result();
       cached = true;
   }
   virtual void modify() {
       cached = false;
       base::modify();
   }
};
class cache_on_write : base {
   int result_value;
   virtual int result() const {
      return result_value;
   }
   virtual void modify() {
      base::modify();
      result_value = base::result(); 
   }
};

型はデータへのcache_on_read変更をキャプチャし、結果を無効としてマークするため、値の次の読み取りで再計算されます。これは、要求に応じて計算を実行するだけなので、書き込み数が比較的多い場合に適したアプローチです (つまり、複数の変更によって再計算がトリガーされることはありません)。これcache_on_writeは、書き込みの数が少なく、読み取りの決定論的なコストが必要な場合に適した戦略となる可能性があります (読み取りの低レイテンシーを考えてください)。

さて、元の問題に戻ります。どちらのキャッシュ戦略も、ベースよりも厳密な一連の不変条件を維持します。最初のケースでは、追加の不変条件はcached、最後の読み取り後に変更されていないtrue場合のみです。data2 番目のケースでは、余分な不変条件は、result_value常に操作の値です。

3 番目の派生型が a への参照を取得し、(許可されている場合) 書き込みbaseにアクセスすると、派生型の不変条件が壊れます。dataprotected

そうは言っても、その特定の結果を達成するためにバックドアを残すため、言語の仕様は壊れています(個人的な意見)。特に、派生型のベースからメンバーのメンバーへのポインターを作成する場合、アクセスは にチェックインされますderivedが、返されるポインターは のメンバーへのポインターであり、任意のオブジェクトbaseに適用できます。 base

class base {
protected:
   int x;
};
struct derived : base {
   static void modify( base& b ) {
      // b.x = 5;                        // error!
      b.*(&derived::x) = 5;              // allowed ?!?!?!
   }
}
于 2012-07-24T15:21:29.960 に答える
3

どちらの例でもFoo、保護されたメソッドを継承していfooBaseます。ただし、最初の例では、同じクラスから指定された保護されたメソッドにアクセスしようとしますが (Foo::foo呼び出しFoo::fooBase)、2 番目の例では、フレンド クラスとして宣言されていない別のクラスから保護されたメソッドにアクセスしようとします (Foo::foo呼び出しを試みますFooBase::fooBase、どちらが失敗しても、後者は保護されます)。

于 2012-07-24T13:25:06.870 に答える
1

最初の例では、Foo 型のオブジェクトを渡します。これは明らかにメソッド fooBase() を継承しているため、それを呼び出すことができます。2 番目の例では、保護された関数を呼び出そうとしています。どのコンテキストでも、そのように宣言されているクラス インスタンスから保護された関数を呼び出すことはできません。最初の例では、保護されたメソッド fooBase を継承しているため、WITHIN Foo コンテキストで呼び出す権利があります。

于 2012-07-24T13:29:05.240 に答える
1

私はコンセプトやメッセージの観点から物事を見る傾向があります。FooBase メソッドが実際に "SendMessage" と呼ばれ、Foo が "EnglishSpeakingPerson" で FooBase が SpeakingPerson である場合、保護された宣言は、SendMessage を EnglishSpeakingPersons (およびサブクラス: AmericanEnglishSpeakingPerson、AustralianEnglishSpeakingPerson) の間で制限することを目的としています。SpeakingPerson から派生した別の型の FrenchSpeakingPerson は、FrenchSpeakingPerson をフレンドとして宣言しない限り、SendMessage を受信できません。「フレンド」とは、FrenchSpeakingPerson が EnglishSpeakingPerson から SendMessage を受信する特別な能力を持っている (つまり、英語を理解できる) ことを意味します。

于 2012-07-24T14:00:36.597 に答える