【Effective C+】構築/プロファイリング/付与演算


文書ディレクトリ
  • 一、C++が黙々とどの関数を呼び出したかを知る
  • 1、コンパイラがデフォルトで呼び出す関数
  • 2、
  • を覚えておいてください
  • 二、コンパイラが自動的に生成する関数を使用したくない場合は、
  • を明確に拒否する.
  • 1、コンパイラの自動生成を拒否する関数
  • 2、
  • を覚えておいてください
  • 三、マルチステートベースクラスにvirtual構造関数
  • を宣言する
  • 1、通常のベースクラスのポインタは派生クラスのオブジェクト
  • を指す.
  • 2、virtual構造関数の役割
  • 3、
  • を覚えておいてください
  • 四、異常を解析関数
  • から逃がすな
  • 1、構造関数に異常が発生しました.何か問題がありますか.
  • 2、
  • を覚えておいてください
  • 五、virtual関数
  • は、構築および構築中に呼び出されない.
  • 1、構築および構築中にvirtual関数を呼び出すにはどのような問題がありますか?
  • 2、
  • を覚えておいてください
  • 六、operator=reference to*this
  • を返す
  • 1、連鎖賦値
  • 2、
  • を覚えておいてください
  • 七、operator=で「自己付与」
  • を処理
  • 1、自己付与を正しく行う
  • 2、
  • を覚えておいてください
  • 8、複製対象の各成分
  • を忘れないでください.
  • 1、オブジェクトを正しくコピーする
  • 2、
  • を覚えておいてください
    一、C++が黙々とどの関数を呼び出したかを知る
    1、コンパイラがデフォルトで呼び出す関数
     コンパイラは、classのdefaultコンストラクション関数、copyコンストラクション関数、copy assignmentオペレータ、およびコンストラクション関数を暗黙的に作成できます.
    class Empty
    {
    public:
    	Empty(){}
    	Empty(const Empty& rhs){}
    	Empty& operator=(const Empty& rhs){}
    	~Empty(){}
    };
    Empty e1; // default    ,    。
    Empty e2(e1); // copy    。
    e2 = e1; // copy assignment   
    

    これらの関数が必要な場合、これらの関数はコンパイラによって作成されます.これらは最も一般的で、最も多く使用されています.では、コンパイラが作成したこれらの関数は何をしていますか?
    まず、コンストラクタがない場合、コンパイラはベースクラスとnon-staticメンバー変数のコンストラクタを呼び出すデフォルトのコンストラクタを作成します.
     構造関数が虚関数であるかどうかは、ベースクラスを継承し、ベースクラスがない場合はnon-virtualがデフォルトです.構造関数は、ベースクラスとnon-staticメンバー変数の構造関数を呼び出します.コンパイラが作成したコピーコンストラクタと付与コンストラクタは、浅いコピーです.
     コンパイラによって作成されたレプリケーションコンストラクタおよび付与オペレータで、メンバー変数を初期化または付与すると、メンバー変数の付与コンストラクタおよび付与オペレータが呼び出されます.
    2、覚えておいてください
     コンパイラは、classのdefaultコンストラクション関数、copyコンストラクション関数、copy assignmentオペレータ、およびコンストラクション関数を暗黙的に作成できます.
    二、コンパイラで自動的に生成される関数を使用したくない場合は、明確に拒否する
    1、コンパイラによる自動生成を拒否する関数
    すべてのコンパイラが生成した関数はpublicです.これらの関数が作成されるのを阻止するには、自分で宣言しなければなりませんが、publicとして宣言する必要はありません.コピーコンストラクタと付与オペレータは使用すべきではない場合があるので、copyコンストラクタまたはcopy assignmentオペレータをprivateとして宣言したりdeleteを使用したりすることができます.
    class HomeForSale
    {
    public:
    	/....../
    private:
    	HomeForSale(const HomeForSale&);
    	HomeForSale& operator=(const HomeForSale&);
    };
    // C++11     delete   
    class HomeForSale
    {
    public:
    	HomeForSale(const HomeForSale&) = delete;
    	HomeForSale& operator=(const HomeForSale&) = delete;
    };
    

    エラーをコンパイルフェーズに早めるのが最善です.早ければ早いほどエラーが発生するほうがいいです.継承によって実現でき、コピーできないクラスを設計できます.
    class Uncopyable
    {
    protected:
        Uncopyable() {}  //   derived       
        ~Uncopyable()  {}
    private:
        Uncopyable(const Uncopyable&); //    copying
        Uncopyable& operator=(const Uncopyable&);
    };
    
    class HomeForSale:private Uncopyable
    {
       /......./ // class     copying  
    };
    

    2、覚えておいてください
      コンパイラが自動的に(暗黙的に)提供する機能を却下するために、対応するメンバー関数をprivateとして宣言し、deleteキーワードを実装したり使用したりすることができます.Uncopyableのようなbase classを使用するのも方法です.
    三、マルチステートベースクラスにvirtual構造関数を宣言する
    1、普通のベースクラスのポインタは派生クラスのオブジェクトを指す
    #include 
    using namespace std;
    
    class Virtualbase
    {
    public:
        void Demon() { cout << "this is Virtualbase class" << endl; };
        void Base() { cout << "this is farther class" << endl; };
    };
    class SubVirtual :public Virtualbase
    {
    public:
        void Demon() 
        {
            cout << "this is SubVirtual!" << endl;
        }
        void Base() 
        {
            cout << "this is subclass Base" << endl;
        }
    };
    void main()
    {
        Virtualbase* inst = new SubVirtual();
     	//            ,                。
        inst->Demon();
        inst->Base();
        /....../
    }
    /* 
        :
    this is Virtualbase class
    this is farther class
    */
    
    #include 
    using namespace std;
    
    class Virtualbase
    {
    public:
        virtual void Demon() { cout << "this is Virtualbase class" << endl; };
        virtual void Base() { cout << "this is farther class" << endl; };
    };
    class SubVirtual :public Virtualbase
    {
    public:
        void Demon() 
        {
            cout << "this is SubVirtual!" << endl;
        }
        void Base() 
        {
            cout << "this is subclass Base" << endl;
        }
    };
    void main()
    {
        Virtualbase* inst = new SubVirtual();
     	//           ,                。
        inst->Demon();
        inst->Base();
        /....../
    }
    /* 
        :
    this is SubVirtual!
    his is subclass Base
    */
    

    2、virtual構造関数の作用
      derived classオブジェクトがbase classポインタを介して削除され、そのbase classはnon-virtual構造関数を持っていて、その結果は定義されていません.実際に実行すると、オブジェクトのderived成分が破棄されず、base class成分が通常破棄され、奇妙な「ローカル破棄」オブジェクトが発生します.これらのクラスを使用する場合、基本クラスポインタまたは参照によって使用されることが多い(クラスのインスタンスはスタック上にある).deleteポインタによってオブジェクトを解析する場合、構造関数が虚関数でない場合、現在のポインタがオブジェクトを指す構造関数は呼び出されません.これはマルチステートの原理です.同じように、マルチステートの関数を実現するには、ベースクラスでも虚関数として宣言されます.
    【Note】:(1)クラスがベースクラスではない場合、その解析関数を虚関数として宣言するのはまずいアイデアです.虚構関数は虚関数テーブルによって呼び出され、虚関数を呼び出すときにポインタ操作が多くなります.それ以外に、オブジェクトが占有するメモリ領域にも虚関数ポインタが1つ増えます.
    3、覚えておいてください
  • polymorphic(多様性を有する)base classesはvirtual構造関数を宣言すべきである.classにvirtual関数がある場合は、virtual構造関数を持つ必要があります.
  • Classesの設計目的は、base classとして使用されないか、または条項06-2のベースクラスUncopyableのような多態性を備えるためでなければ、virtual構造関数を宣言するべきではない.

  • 四、異常を構造関数から逃がすな
    1、解析関数に異常が発生しました.何か問題がありますか.
    class DBConnection {
    public : 
        /....../
        static DBConnection create(); //       DBConnection   
        void close(); //    ;       。
    };
    
    class DBConn {  //   class    DBConnection   
    public : 
         /....../
         ~DBConn()  //              
         {
         	  db.close();
         }
    private : 
         DBConnection db;
    };
    

    呼び出しcloseが成功すれば、すべてが素晴らしいです.しかし、異常が発生すると、DBConnは異常を放出します.つまり、この異常が構造関数から離れることを許可し、異常が伝播します.
    1つの比較的良い戦略はDBCoonインタフェースを再設計することであり、お客様が発生する可能性のある異常に反応できることです.例えば、DBConnは自分でclose関数を提供し、お客様に「この操作によって発生した異常」を処理する機会を与えることができます.DBConnは、管理するDBConnectionがオフになっているかどうかを追跡し、答えがNOの場合はその構造関数によってオフにすることで、データベース接続の紛失を防止することもできます.しかし、DBConnectionの解析関数がcloseを呼び出すのに失敗した場合、問題はまた起点に戻ります.
    class DBConn {
    public:
       .....
       void close()        //         
       {
           db.close();
           closed = true;
        }
        ~DBConn()
        {
           if (!closed) {
                try { db.close(); }
                catch( ... ){
                       //   
                       ....
                    }
             }
        }
    private:
        DBConnection db;
        bool closed;
    };
    

    2、覚えておいてください
  • 構造関数は絶対に異常を吐かないでください.構造関数によって呼び出された関数が例外を放出する可能性がある場合は、構造関数は任意の例外をキャプチャし、それらを飲み込む(伝播しない)か、プログラムを終了する必要があります.
  • お客様が操作関数の実行中に投げ出された異常に反応する必要がある場合、classは、構造関数ではなく一般的な関数を提供して操作を実行する必要があります.

  • 五、virtual関数は構築と分析の過程で決して呼び出さない
    1、構築と分析中にvirtual関数を呼び出すのに何か問題がありますか?
    class Transaction 
    {
    public:
         Transaction ();
         //               
         virtual void logTransaction () const = 0;     
         /....../
    };
    Transaction::Transaction ()
    {
         /....../
         logTransaction ();
    }
    class BuyTransaction :public Transaction 
    {
    public:
         virtual void logTransaction () const;
         /....../
    };
    class SellTransaction :public Transaction 
    {
    public:
         virtual void logTransaction () const;
         /....../
    };
    //      
    BuyTransaction b;
    

      base classコンストラクション関数の実行はderived classコンストラクション関数よりも早いため、base classコンストラクション関数の実行時にderived classのメンバー変数はまだ初期化されていません.derived classオブジェクトのbase class構造中、オブジェクトのタイプはderived classではなくbase classです.
      ソリューション:base class内でvirtual関数をnon-virtualに変更し、derived class構造関数に必要な情報をbase class構造関数に渡すように要求します.その後、base class構造関数はnon-virtual関数を安全に呼び出すことができます.次のようになります.
    class Transaction 
    {
    public:
    	  //       ,    explicit           
         explicit Transaction (const std::string& logInfo);   
         //non-virtual  
         void logTransaction (const std::string& logInfo) const;     
         /....../
    };
    Transaction::Transaction (const std::string& logInfo)
    {
         /....../
         logTransaction (logInfo);  //non-virtual  
    }
    class BuyTransaction :public Transaction 
    {
    public:
    	//  log    base class     
        BuyTransaction(parameters)
        :Transaction(createLogString(parameters))  
        {   /......./   }
        /....../
    private:
    	 //    static
         static std::string createLogString(parameters);  
    };
    

    2、覚えておいてください
  • は、このような呼び出しがderived class(現在実行されているコンストラクション関数およびコンストラクション関数の階層よりも)に低下しないため、コンストラクションおよびコンストラクションの間virtual関数を呼び出さないでください.これは、virtual関数の構造/解析中の「異常表現」、すなわち、virtual関数はvirtual関数ではないということである.

  • 六、operator=reference to*thisを返す
    1、連鎖賦課
    int x, y, z;
    x = y = z = 15;    //       
    //         ,            :
    x = (y = (z = 15));
    

      ここで15は、まずzに割り当てられ、その後、その結果(更新後のz)がyに割り当てられ、その後、その結果(更新後のy)がxに割り当てられる.「連鎖割り当て」を実現するために、割り当てオペレータは、オペレータの左側を指すreferenceを返さなければならない.
    class Widget
    {
    public:
    	/....../
    	Widget &operator=(const Widget &rhs)
    	{
    		/....../
    		return *this;
    	}
    };
    

    2、覚えておいてください
  • は、割り当てオペレータにreference to*thisを返します.

  • 七、operator=で「自己付与」を処理する
    1、自己賦課を正しく行う
    class Widget
    {
    public:
    	void swap(const Widget& rhs);//  rhs this
    	Widget& operator=(const Widget& rhs)
    	{
    		Widget tmp(rhs); //       
    		swap(tmp) //   
    		return *this; //          
    	}
    	int *p;
    };
    

    2、覚えておいてください
  • は、オブジェクトが自己割り当てされたときにoperator=が良好に動作することを保証します.テクノロジーには、「ソース・オブジェクト」と「ターゲット・オブジェクト」のアドレスの比較、周到な文の順序、copy-and-swapが含まれます.
  • は、いずれかの関数が1つ以上のオブジェクトを操作し、複数のオブジェクトが同じオブジェクトである場合でも、その動作が正しいことを決定する.

  • 八、複製対象の各成分を忘れない
    1、オブジェクトを正しくコピーする
    1つのクラスには、コピーコンストラクション関数と付与オペレータの2つの関数があり、copying関数と総称されます.自分で2つの関数を作成しないと、コンパイラはこの2つの関数を実装し、コンパイラが生成したバージョンはオブジェクトのすべてのメンバー変数をコピーします.コンパイラが生成するcopying関数のやり方は、通常、浅いコピーです.もし私たちがcopying関数を実現したら、コンパイラは私たちを実現しません.しかし、コンパイラはcopying関数がオブジェクトの各変数に値を付与しているかどうかを確認しません.
    class PriorityCustomer : public Cutsomer
    {
    public:
    	PriorityCustomer()
    	{
    		cout<<"PriorityCustomer Ctor"<<endl;
    	}
    	PriorityCustomer(const PriorityCustomer& rhs)
    		:priority(rhs.priority)
    	{
    		cout<<"PriorityCustomer Copy Ctor"<<endl;
    	}
    	PriorityCustomer& operator=(const PriorityCustomer& rhs)
    	{
    		cout<<"PriorityCustomer assign operator"<<endl;
    		priority=rhs.priority;
    		return *this;
    	}
    private:
    	int priority;
    };
    PriorityCustomer(const PriorityCustomer& rhs)
    		:Cutsomer(rhs),priority(rhs.priority)
    {
    	cout<<"PriorityCustomer Copy Ctor"<<endl;
    }
    PriorityCustomer& operator=(const PriorityCustomer& rhs)
    {
    	cout<<"PriorityCustomer assign operator"<<endl;
    	Cutsomer::operator=(rhs);
    	priority = rhs.priority;
    	return *this;
    }
    

    2、覚えておいてください
  • Copy関数は、「オブジェクト内のすべてのメンバー変数」および「すべてのbase class成分」がコピーされていることを確認します.
  • あるcopying関数で別のcopying関数を実装しようとしないでください.共通機能を3番目の関数に入れ、2つのcopying関数で共通に呼び出す必要があります.