C++霧中風景1:友元類とオブジェクト向け

4938 ワード

後から入社予定の会社ではC++への転職を希望していたので、最近も順番にC++の勉強を始めています.そしてこのシリーズの文章はC++の言語特性を探究し、Java、Scala、Python、Goなどの異なる言語間の設計哲学を比較するとともに、レンガを投げて玉を引く希望を持っている.最近、学習過程で友元関数と友元類の概念に触れ、最初の文章では友元の概念について話します.
1.友元関数:
冒頭では、友元という概念を簡単に紹介しましょう.C++では、クラスのメタ関数はクラスの外部に定義されていますが、クラスのすべてのプライベートメンバーと保護メンバーにアクセスする権利があります.メタ関数のプロトタイプはクラスの定義に現れたことがあるが、メタ関数はメンバー関数ではない.友元は、友元関数と呼ばれる関数であってもよい.友元は、友元類と呼ばれるクラスであってもよい.
直接上のコードを見て、友元関数と友元クラスが具体的にどのように使用されているかを見てみましょう.
#include 
using namespace std;

class Box {
public:
    Box(double l, double b, double h) {
        length = l;
        breadth = b;
        height = h;
    }
    friend class A;
    friend void boxPrintBox(Box &box);
private:
    double length;
    double breadth;
    double height;
};

//    ,          
void boxPrintBox(Box &b) {
    cout << b.height << " " << b.length << " " << b.breadth << endl;
}

//   
class A {
public:
    void printBox(Box &b) {
        cout << b.height << " " << b.length << " " << b.breadth << endl;
    }
};


int main() {
    Box box(1,2,3);
    
    //    ,    Box  private   
    boxPrintBox(box);
    //   ,      Box  private   
    A a;
    a.printBox(box);
        
    return 0;
}

上のコードから、友元関数と友元クラスは、オブジェクトのプライベート変数に直接アクセスできることがわかります.次に,友元関数の特徴を解析する.
  • 1、なぜ友元関数を導入するのか:クラス間のデータ共有を実現する際に、システムのオーバーヘッドを減らし、効率を高める.具体的には、他のクラスのメンバー関数がクラスのプライベート変数に直接アクセスできるようにします.すなわち、外部のクラスまたは関数がクラスのプライベート変数と保護変数にアクセスし、2つのクラスが同じ関数を共有できるようにします.効率を高めることができ、表現が簡単で、はっきりしている.
  • 2、Friend関数をいつ使用するか:1)演算子をリロードする場合、Friendを使用する必要がある場合があります.2)2つのクラスがデータを共有する場合
  • 3、友元方式の欠点:1)友元関数はパッケージメカニズムを破り、やむを得ない場合を除いて友元関数を使用する.

  • 2.友元関係と対象:
    次に、オブジェクト向けの観点から友元関係を理解する方法についてお話しします.(以下の内容はすべて個人的な理解であり、不正確な点があれば斧正を望む)
  • 1)友元関数友元関数はクラスに依存しない関数であり,クラスにアクセスできるプライベート変数を除いてクラス外部に実装される他の関数と変わらない.オブジェクト向けの観点から,関数はクラス外に独立して実現すべきではない.明らかに独立とクラス以外の友元関数は,オブジェクト向けの観点から考えると,優雅ではない解決策である.オペレータ<
  • friend ostream &operator<

    明らかに、これは<
    friend ostream &operator<

    Java、Scala、Pythonのように、クラスの存在定義とは独立した関数はサポートされていません.オブジェクト向けの観点では,後続の言語がより純粋に実現される.したがって,自身のコードスタイルがオブジェクト向けのスタイルに近づくと,友元関数ではなく友元クラスが必要な機能を実現するためにできるだけ理由をつけなければならない.Pythonでは直接defで関数を定義できるのに、クラスも必要ないし、オブジェクト向けの論理的思考にも合わないのではないでしょうか.ここで簡単に説明すると、Pythonの各関数は、functionオブジェクトにパッケージされているので、Pythonの中にはすべてのオブジェクトがあり、独立してクラスが存在する関数は存在しません.JavaとScalaのlambda式のように匿名クラスとしてパッケージされています.
  • 2)友元類は友元類の包装により,友元類が一変し,類の中のすべての方法が友元関数となった.上記のオブジェクト向けの論理を破壊することはないように見えますが、継承には大きな穴があります.私たちは一緒に撫でてみましょう.友元関係は継承できません.ベースクラスの友元は、派生クラスのメンバーに特別なアクセス権を持っていません.基本クラスに友元関係が付与されている場合、友元関係が付与されているクラスには特別なアクセス権があるベースクラスのみがアクセスできます.複雑ですね.直接コードします.
  • #include 
    using namespace std;
    
    class A {
    private:
        int x;
    friend class C;
    };
    
    class B:public A{
    private:
        int y;
    };
    
    class C {
        void printA(A& a) {
            cout << a.x << endl;
        }
        void printB(B& b) {
            cout << b.x << endl; //C              B  A         
            //cout << b.y << endl; C      B      ,       ,      。
        }
    };
    

    クラスCとAの友元関係は継承にとどまり,クラスCはクラスBが新たに定義したプライベート変数にアクセスできないことは明らかである.(ここでは、クラスBがクラスAのプライベート変数xを上書きしている場合、クラスCのprintBはコンパイルできますか?)
    別のコードを見てみましょう
    #include 
    using namespace std;
    
    class A
    {
        int x;
    public:
        friend class B;
    };
    class B
    {
    public:
        void fun(A& a){ cout << a.x << endl;}
    };
    
    class C:public B
    {
    public:
        //void fun2(A& a){ cout <

    クラスCはクラスBを継承したが、Aとの友元関係も持たず、「お父さん」になるしかない.クラスAには、クラスBから継承された友元関数に依存してアクセスする.△クラスBのfun関数がprotectedまたはprivateであれば、上記のコードは正常にコンパイルできますか.
    ここで簡単な結びつきをします:友元関係は友元類の中で継承性がなくて、基類の友元関係に頼るしかありません.
    3.非C++言語はどのように友元関係を解決したのか:
  • Java JAVA修飾子タイプ(public,protected,private)
  • publicのクラス、クラス属変数と方法、パッケージ内とパッケージ外の任意のクラスにアクセスできます.
  • protectedのクラス、クラス属変数および方法、パケット内の任意のクラス、およびパケット外のそれらがこのようなサブクラスを継承してこそアクセスできる.
  • privateのクラス、クラス属変数および方法は、パッケージ内のパッケージ外の任意のクラスにアクセスできません.
  • クラス、クラス変数、およびメソッドがこの3つの修飾子で修飾されない場合.パケット内の任意のクラスはアクセスできますが、パケット外の任意のクラスはアクセスできません(パケット外がこのようなサブクラスを継承していることを含む).だからこのタイプはfriendlyタイプとも呼ばれることがありますが、今ではこの名前の出所がわかりますよね.同じパッケージにどんな種類を入れるか、新しい認識がありますか?

  • ScalaはScalaの中でprivateとprotectedは追加のパラメータを指定することができる.private[AccessQualifier]を使用することができ、AccessQualifierはthisであってもよいし、他のクラス名またはパッケージ名であってもよい.これにより、このメンバーはすべてのクラスに対してprivateであり、自分とAccessQualifierが表す範囲内のクラスを除くと理解できます.この概念も繰返し可能であり、すなわち、AccessQualifierがクラスである場合、privateメンバーはAccessQualifierのAccessQualifierに対しても可視である.(優雅な方法ですね.私はScalaが好きです.)
  • Python老子彼ニャーはアクセス制御がなく、自覚に頼っている.
  • Golangは乱暴で、アルファベットの大文字と小文字でアクセス制御を区別しているので、より細粒度の制御はできません.しかし、ほとんどのシーンのエンジニアリング実装にも影響しないように見えます.だからこのように簡素化された設計哲学も、優雅な解決策ではないだろうか.