c++ アクセス - 派生クラスがこのコードで保護されたメンバ関数を呼び出せないのはなぜですか?




メンバ変数 基底 (5)

#include <iostream>

class Base
{  
protected:
    void somethingProtected()
    {
        std::cout << "lala" << std::endl;
    }
};

class Derived : public Base
{
public:
    void somethingDerived()
    {
        Base b;
        b.somethingProtected();    // This does not compile
        somethingProtected();      // But this is fine
    }
};

int main()
{
    Derived d;
    d.somethingDerived();
    return 0;
}

保護されたメンバーのみが使用でき、他のインスタンスの保護されたメンバーは永遠に到達できないと考えました。

しかし:

class Derived : public Base
{
public:

    void somethingDerived(Derived& d)
    {
        d.somethingProtected();  // This compiles even though d is
                                 // potentially a different instance
    }

    void somethingDerived(Base& b)
    {
        b.somethingProtected();  // This does not
    }
};

私はしばらくの間、C ++でプログラミングしていたので、これでうんざりしているように感じましたが、この動作の説明は見つかりませんでした。

編集:

同じインスタンスか別のインスタンスかは関係ありません。

int main()
{
    Derived d1, d2;          // Two different instances
    d1.somethingDerived(d2); // This compiles fine
    d1.somethingDerived(d1); // This compiles fine
    return 0;
}

EDIT2:

アクセス権に関しては、クラスのどのインスタンスが使用されているかはまったく重要ではないようです。

class Base
{
public:
    void something(Base& b)  // Another instance
    {
        ++b.a;               // But can enter private members
    }

private:
    int a;
};

Answers

私は基本クラスのメンバーにアクセスする方法にいくつかの混乱があると信じています。 それはこの方法だけです:

class Derived : public Base
void drivedMethod() {
    Base::baseMethod();
}

あなたの例では、別のインスタンスの保護されたメンバーにアクセスしようとしています。

派生インスタンスは、自身の保護されたメンバーにアクセスできますが、別のクラスインスタンスの保護されたメンバーにはアクセスしません。これは設計によるものです。

実際には、別のクラスの保護されたメンバーに、別のインスタンスメンバーから、またはメイン関数からアクセスしているのは、実際には両方とも公開アクセスの下にあります...

http://www.cplusplus.com/doc/tutorial/inheritance/ (さまざまなレベルを見るためのアクセス指定テーブルを参照)

どちらの例も同じ例です。

void somethingDerived(Base& b)
    {
        b.somethingProtected();  // This does not

ここでDerivedクラスはパラメータとしてbを取得していますので、b.somethingProtectedはpublicではないため、別のインスタンスのbaseを取得しています。

これは準拠します:

void somethingDerived()
{
   Base::somethingDerived();

もう1つのdクラスのパブリックメソッドにアクセスしているため、2番目の例は正常に準拠しています

>  void somethingDerived(Base& b)
>     {
>         b.somethingProtected();  // This does not
>     }

Derivedクラスは、 Derivedオブジェクト内の保護された基本メンバにのみアクセスできます。 (必然的に) Derivedオブジェクトではないオブジェクト内のメンバにアクセスすることはできません。 失敗した場合、 Base &を介してメンバーにアクセスしようとしています。これはDerivedではないオブジェクトを参照する可能性があるため、アクセスできません。


C ++でのアクセス制御は、(インスタンス単位ではなく)クラス単位で動作しますが、 protectedアクセス指定子にはいくつかの特質があります。

言語仕様では、派生クラスに属する基本サブオブジェクトの保護されたメンバーに確実にアクセスする必要があります。 無関係な基本型の独立したオブジェクトの保護されたメンバーにアクセスすることはできません。 特に、基底型のフリースタンディングオブジェクトの保護されたメンバーにアクセスすることはできません。 ベースサブオブジェクトとして派生オブジェクトに埋め込まれた基本オブジェクトの保護されたメンバーにのみアクセスできます。

このため、 pointer->member構文、 reference.memberまたはobject.member構文で保護されたメンバーにアクセスする必要がpointer->memberます。ポインタ/参照/オブジェクトは派生クラスを参照します。

つまり、あなたの例では、保護されたメンバーのsomethingProtected()は、 Baseオブジェクト、 Base *ポインタまたはBase & referencesからアクセスできませんが、 Derivedオブジェクト、 Derived *ポインタ、 Derived & Reference Derived *アクセスできます。 あなたは普通のsomethingProtected()アクセスが許可されています。 thisthis->somethingProtected()省略形であり、 thisDerived *型です。

b.somethingProtected()は上記の要件に違反します。

上記の規則に従って

void Derived::somethingDerived()
{
    Base *b = this;
    b->somethingProtected();    // ERROR
    this->somethingProtected(); // OK
}

両方とも同じエンティティにアクセスしようとしても、最初の呼び出しは失敗し、2番目の呼び出しはコンパイルされます。


あなたがしたことはC ++では違法です。 保護されたメンバーには、クラスのオブジェクトからアクセスすることはできません。 メンバー関数のみが保護されたメンバーにアクセスできます。 protectedメンバーは、派生クラスによって継承されている場合を除いて、privateメンバーと同様に動作します。 プライベート、パブリック、および保護されたメンバーの違いを理解するために、以下のプログラムを検討してください。

class Base
{
    private:
    void somethingPrivate()
    {
        std::cout << "sasa" << std::endl;
    }
    public:
    void somethingPublic()
    {
        std::cout << "haha" << std::endl;
    }
    protected:
    void somethingProtected()
    {
        std::cout << "lala" << std::endl;
    }
};

class Derived : public Base
{
public:
    void somethingDerived()
    {
       Base b;
       b.somethingPublic();   // Works fine.
       somethingProtected();  // This is also fine because accessed by member function.
       //b.somethingProtected();  // Error. Called using object b.
       //somethingPrivate();      // Error. The function is not inherited by Derived.
    }
};

32ビットの値を保存するよりも、32ビットのシステムで4倍の時間が必要なのはなぜですか?

それはしません。 しかし、ベンチマークには3つの問題があり、それらの結果が得られます。

  1. あなたは記憶をあらかじめフォールトさせているわけではありません。 つまり、ベンチマーク中に配列をページフォルトしています。 これらのページフォルトとOSカーネルの相互作用は、当時の主な要因です。
  2. -O3を指定したコンパイラは、すべてのループをmemset()変換してベンチマークを完全に破っています。
  3. ベンチマークはメモリにバインドされています。 だからあなたはコアの代わりにあなたの記憶のスピードを測定しています。

問題1:テストデータが事前予防されていない

あなたの配列は宣言されていますが、ベンチマークの前には使用されていません。 カーネルとメモリの割り当てが機能するため、メモリにはまだマップされません。 これが最初に触れるときだけです。 そして、そうすると、カーネルからページをマップするために非常に大きなペナルティが発生します。

これは、ベンチマークの前にすべての配列をタッチすることによって行うことができます。

Pre-Faultingなし: http://coliru.stacked-crooked.com/a/1df1f3f9de420d18 : http://coliru.stacked-crooked.com/a/1df1f3f9de420d18

g++ -O3 -Wall main.cpp && ./a.out
Time of processing int8 array:  28983us.
Time of processing int16 array: 57100us.
Time of processing int32 array: 113361us.
Time of processing int64 array: 224451us.

Pre-Faulting: http://coliru.stacked-crooked.com/a/7e62b9c7ca19c128 : http://coliru.stacked-crooked.com/a/7e62b9c7ca19c128

g++ -O3 -Wall main.cpp && ./a.out
Time of processing int8 array:  6216us.
Time of processing int16 array: 12472us.
Time of processing int32 array: 24961us.
Time of processing int64 array: 49886us.

つまり、元のベンチマークは実際のコードより多くのカーネルを測定していました。

問題2:コンパイラがベンチマークを破っている

コンパイラはあなたの書き込みゼロのパターンを認識しており、すべてのループをmemset()呼び出しで完全に置き換えています。 実際には、サイズの異なるmemset()呼び出しを測定しています。

  call std::chrono::_V2::system_clock::now()
  xor esi, esi
  mov edx, 67108864
  mov edi, OFFSET FLAT:int8Array
  mov r14, rax
  call memset
  call std::chrono::_V2::system_clock::now()
  xor esi, esi
  mov edx, 134217728
  mov edi, OFFSET FLAT:int16Array
  mov r13, rax
  call memset
  call std::chrono::_V2::system_clock::now()
  xor esi, esi
  mov edx, 268435456
  mov edi, OFFSET FLAT:int32Array
  mov r12, rax
  call memset
  call std::chrono::_V2::system_clock::now()
  xor esi, esi
  mov edx, 536870912
  mov edi, OFFSET FLAT:int64Array
  mov rbp, rax
  call memset
  call std::chrono::_V2::system_clock::now()

これを行う最適化は-ftree-loop-distribute-patternsです。 たとえそれをオフにしても、ベクタライザはあなたに同様の効果を与えます。

-O2 、ベクトル化とパターン認識の両方が無効になります。 だから、コンパイラはあなたが書いたものをあなたに与えます。

.L4:
  mov BYTE PTR [rax], 0         ;; <<------ 1 byte at a time
  add rax, 1
  cmp rdx, rax
  jne .L4
  call std::chrono::_V2::system_clock::now()
  mov rbp, rax
  mov eax, OFFSET FLAT:int16Array
  lea rdx, [rax+134217728]
.L5:
  xor ecx, ecx
  add rax, 2
  mov WORD PTR [rax-2], cx      ;; <<------ 2 bytes at a time
  cmp rdx, rax
  jne .L5
  call std::chrono::_V2::system_clock::now()
  mov r12, rax
  mov eax, OFFSET FLAT:int32Array
  lea rdx, [rax+268435456]
.L6:
  mov DWORD PTR [rax], 0        ;; <<------ 4 bytes at a time
  add rax, 4
  cmp rax, rdx
  jne .L6
  call std::chrono::_V2::system_clock::now()
  mov r13, rax
  mov eax, OFFSET FLAT:int64Array
  lea rdx, [rax+536870912]
.L7:
  mov QWORD PTR [rax], 0        ;; <<------ 8 bytes at a time
  add rax, 8
  cmp rdx, rax
  jne .L7
  call std::chrono::_V2::system_clock::now()

-O2http://coliru.stacked-crooked.com/a/edfdfaaf7ec2882e : http://coliru.stacked-crooked.com/a/edfdfaaf7ec2882e

g++ -O2 -Wall main.cpp && ./a.out
Time of processing int8 array:  28414us.
Time of processing int16 array: 22617us.
Time of processing int32 array: 32551us.
Time of processing int64 array: 56591us.

今では、より小さな単語サイズが遅いことは明らかです。 しかし、すべての単語サイズが同じ速度であれば、時間が平らになると予想されます。 その理由はメモリ帯域幅のためではありません。

問題3:メモリ帯域幅

ベンチマーク(書面による)はゼロを書いているだけなので、コア/システムのメモリ帯域幅は容易に飽和しています。 したがって、ベンチマークは、どれだけのメモリに触れたかによって影響を受けます。

これを修正するには、キャッシュに収まるようにデータセットを縮小する必要があります。 これを補うために、同じデータを何度も繰り返します。

std::array<std::int8_t, 512> int8Array;
std::array<std::int16_t, 512> int16Array;
std::array<std::int32_t, 512> int32Array;
std::array<std::int64_t, 512> int64Array;

...

auto point1 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int8Array) v = 0;
auto point2 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int16Array) v = 0;
auto point3 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int32Array) v = 0;
auto point4 = std::chrono::high_resolution_clock::now();
for (int c = 0; c < 64 * 1024; c++) for (auto &v : int64Array) v = 0;
auto point5 = std::chrono::high_resolution_clock::now();

今や、さまざまな単語サイズに対してより多くのフラットなタイミングを見ています。

http://coliru.stacked-crooked.com/a/f534f98f6d840c5c

g++ -O2 -Wall main.cpp && ./a.out
Time of processing int8 array:  20487us.
Time of processing int16 array: 21965us.
Time of processing int32 array: 32569us.
Time of processing int64 array: 26059us.

完全に平坦ではない理由はおそらく、コンパイラの最適化に関連する他の多くの要因があるためです。 近くに近づくには、ループアンローリングに頼る必要があるかもしれません。





c++ oop