web-dev-qa-db-ja.com

別のサブクラスの基本クラスの保護されたメンバーにアクセスする

なぜこれはコンパイルするのですか?

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番目のコードスニペットはコンパイルしません。

36
Kaiserludi

fooFooBase参照を受け取った場合、コンパイラは引数がFooの子孫であるかどうかを認識していないため、そうでないと想定する必要があります。 Fooother Foo objectsの継承された保護メンバーにアクセスできますが、他のすべての兄弟クラスにはアクセスできません。

このコードを考えてみましょう:

class FooSibling: public FooBase { };

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

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

Fooが、すべてのFooBaseオブジェクトの保護されたメンバーにアクセスする必要がある場合、Fooの子孫であることがわかっているオブジェクトだけでなく、FooFooBaseの友達:

class FooBase
{
protected:
  void fooBase(void);
  friend class Foo;
};
31
Rob Kennedy

C++ FAQ はこの問題をうまくまとめています:

[あなた]は自分のポケットを選ぶことができますが、父親のポケットや兄弟のポケットを選ぶことはできません。

20
h0b0

重要な点は、protectedは、-any他のオブジェクトのメンバーではなく、メンバーの独自のコピーへのアクセスを許可することです。 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タイプは、データへの変更をキャプチャし、結果を無効としてマークします。これにより、値の次のreadが再計算されます。オンデマンドでのみ計算を実行するため、書き込みの数が比較的多い場合、これは良いアプローチです(つまり、複数の変更によって再計算がトリガーされることはありません)。 cache_on_write事前に結果を事前計算します。これは、書き込みの数が少なく、読み取りの確定的なコストが必要な場合に適しています(読み取りのレイテンシが低いと考えてください)。

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

3番目の派生型がbaseへの参照を取得し、dataにアクセスして書き込みを行った場合(protectedで許可されている場合)、派生型の不変式で壊れます。 。

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

class base {
protected:
   int x;
};
struct derived : base {
   static void modify( base& b ) {
      // b.x = 5;                        // error!
      b.*(&derived::x) = 5;              // allowed ?!?!?!
   }
}

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

3
Zeta

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

1
Moataz Elmasry

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

1
Sentinel

hobo's answer に加えて、回避策を探すことができます。

サブクラスでfooBaseメソッドを呼び出したい場合は、staticにすることができます。静的保護メソッドには、すべての引数を持つサブクラスからアクセスできます。

0
yairchu