『Effective C+』学習ノート(2)

5201 ワード

2構造/解析/付与演算
条項05:C++が黙々と作成して呼び出す関数を理解する
  • 自分が宣言していない場合、コンパイラはクラス宣言(コンパイラバージョンの)のcopyコンストラクタ、copy assignmentオペレータ、および構造関数を作成します.また、コンストラクション関数が宣言されていない場合、コンパイラはdefaultコンストラクション関数を宣言します.これらの関数はすべてpublicでinlineです.
  • これらの関数が必要(呼び出される)場合にのみ、コンパイラによって作成されます.必要に応じて、コンパイラが作成します.
  • defaultコンストラクション関数およびコンストラクション関数は、base classesおよびnon-staticメンバー変数を呼び出すコンストラクション関数およびコンストラクション関数のように、コンパイラの場所に「隠れた背後」のコードを配置するために主に使用されます.注意:コンパイラによって生成された構造関数はnon-virtualです.このクラスのベースクラス自身がvirtual構造関数を宣言しない限り.
  • コピーコンストラクタおよびコピー付与オペレータについては、コンパイラが作成したバージョンは、単純にソースオブジェクトの非静的メンバー変数をターゲットオブジェクトにコピーするだけです.
  • クラスがコンストラクション関数を宣言した場合(パラメータの有無にかかわらず)、コンパイラはデフォルトのコンストラクション関数を作成しません.
  • コンパイラによって生成されたcopy assignmentオペレータ:メンバー変数にポインタ、参照、定数タイプがある場合は、独自の「適切な」コピー付与オペレータの構築を検討する必要があります.同じメモリを指すポインタは潜在的な危険であるため、参照は変更できず、定数は変更できません.

  • **note:**コンパイラは、クラスにdefaultコンストラクション関数、copyコンストラクション関数、copy assignmentオペレータ、およびコンストラクション関数を暗黙的に作成できます.
    条項06:コンパイラによって自動的に生成される関数を使用したくない場合は、明示的に拒否する
    通常、クラスが特定のスキルをサポートしたくない場合は、対応する関数を説明しない限りです.しかし、このポリシーは、コピーコンストラクタとコピー付与オペレータには機能しません.コンパイラは「自作多情」で宣言し、必要に応じて呼び出すからです.
    コンパイラによって生成される関数はpublicタイプであるため、コピーコンストラクタまたはコピー付与オペレータをprivateとして宣言できます.この小さな「手口」により、外部で呼び出されるのを阻止できますが、クラス内のメンバー関数と友元関数はprivate関数を呼び出すことができます.解決策は、コピー動作を阻止するために設計されたベースクラスである可能性がある.(Boostが提供するクラス名はnoncopyable).
  • は、ユーザによるオブジェクトのコピーを許可しない.一般的なコンパイラでは、デフォルトのコピーが提供され、対応するメンバー関数をprivateとして宣言できます.しかし、member(メンバー)関数とfriend(友元)関数は呼び出すことができるという問題があります.
  • は、実現したくない関数に関数パラメータの名前を書かない.お客様がcopyを企てると、コンパイラは彼を妨害します.メンバーまたはfriend関数内でcopyを誤って使用すると、コネクタに問題が発生します.
  • class HomeForSale{
        public:
            ...
        private:
            HomeForSale(const HomeForSale&);
            HomeForSale& operator=(const HomeForSale&);
    };
    
  • コネクタエラーをコンパイル期間に移動し、エラーをより早く発見するとより良いことが多い.Uncopyableのベースクラスを定義し、他のクラスがクラスを継承し、コピーを実行するときにベースクラスコピーコンストラクタを呼び出すと問題が発生します.
  • class Uncopyable{
        protected:
            Uncopyable();
            ~Uncopyable();
        private:
            Uncopyable(const Uncopyable&);
            Uncopyable& operator=(const Uncopyable&);
    };
    //   Uncopyable
    class HomeForSale: private Uncopyable{
      ... ...
    }
    

    **note:**コンパイラが自動的に(暗黙的に)提供する機能を却下するために、対応するメンバー関数をprivateとして宣言し、実装しません.Uncopyableのようなベースクラスを使うのも一つの方法です.
    条項07:マルチステートベースクラスにvirtual構造関数を宣言する
    ベースクラスのポインタが派生クラスのオブジェクトを指す場合、deleteを呼び出すと、ベースクラスの成分は通常破棄され、派生クラスの成分はスタックに残る可能性があります.これは資源の漏れ、データ構造の破綻を形成し、デバッガに多くの時間を消費します.
  • この問題を解消する方法は簡単です.ベースクラスにvirtual構造関数を与えます.その後、派生クラスオブジェクトを削除すると、derived classコンポーネントを含むオブジェクト全体が破棄されます.
  • どのクラスにもvirtual関数がある限り、virtual構造関数もあるはずです.
  • クラスがvirtual関数を含まない場合、通常はベースクラスとして使用されることを意図していないことを示し、クラスがベースクラスとして使用されることを意図していない場合、その構造関数をvirtualとするのはよく悪い考えである.virtual関数を実装するには、追加のオーバーヘッド(虚関数テーブルを指すポインタvptr)が必要です.したがって、classに少なくとも1つのvirtual関数が含まれている場合にのみ、virtual構造関数が宣言されます.
  • STLコンテナはvirtual構造関数を持たないので、派生しないほうがいいです.
  • いくつかのclassesの設計目的はベースクラスとして使用されるが、条項06のUncopyableのような多態用途のためではないため、virtual構造関数は必要ない.

  • ** note: **
  • マルチステート特性を有するベースクラスはvirtual構造関数を宣言すべきである.クラスにvirtual関数がある場合は、virtual構造関数を持つ必要があります.
  • クラスの設計目的は、ベースクラスとして使用されるか、または多態性を備えるためにvirtual構造関数を宣言することではない.

  • 条項08:例外を構造関数から逃がさない
    C++は構造関数が異常を吐くことを禁止していませんが、それを奨励しません.C++は解析関数が異常を吐くのが好きではありません.
    class DBConnection{
        public:
            static DBConnection create();
            void close();
    };
    
    class DBManager{
        public:
            ~DBManager(){
                db.close();  //           
            }
    
        private:
            DBConnection db;
    };
    
    //              
    DBManager dbM(DBConnection::create());
    

    解析関数で呼び出された関数が例外を放出した場合:
  • 異常を投げ出すとプログラムを終了します.強制終了プログラムは合理的なオプションです.結局、構造関数から異常が伝播することを阻止することができます(それは不明確な行為を招きます).通常aboutを呼び出すことで完了します.
  • DBManager::~DBManager(){
        try { db.close(); }
        catch(...){
            //            
            std::abort();
        }
    }
    
  • が発生した異常を飲み込む.異常を捕獲するが、何もしない.異常を飲み込むのは悪い考えだが、異常を飲み込むのも「軽率なプログラム終了」や「不明確な行為によるリスク」を負担するよりはましだ.
  • DBManager::~DBManager(){
        try { db.close(); }
        catch(...){
            //           
        }
    

    しかし、両者とも「close放出異常を招く」状況には反応できない.構造関数が例外をキャプチャしても、お客様は例外を処理できません.お客様は、関数の実行中に放出された例外に反応する必要があります.したがって、classは、この操作を実行するための一般的な関数を提供する必要があります.
    class DBManager(){
        public:
            void close(){
                db.close();
                closed = true;
            }
            ~DBManager(){
                if (!closed){
                    try { db.close(); }
                    catch(...) {
                        //    ...
                    }
                }
            }
        private:
            bool closed;
            DBConnection db;
    };
    

    ここにはclose関数が追加されており、お客様は自分でclose関数を呼び出すことができ、異常が発生した場合、異常処理を行い、構造関数をもう一度閉じ直すことができます(今回も異常であれば、上記の結果と同じです).クライアントがclose関数を呼び出さない場合は、構造関数で自動的に呼び出すことができます.したがって、プログラムを書くときは、異常が発生した関数を通常の関数として使用する必要があります.これにより、より多くの選択が可能になります.
    オペレーションが失敗したときに例外を放出し、例外を処理する必要がある場合は、この例外は構造関数以外の関数から発生する必要があります.
    ** note: **
  • 構造関数は絶対に異常を吐かないでください.構造関数によって呼び出された関数が例外を放出する可能性がある場合は、構造関数は任意の例外をキャプチャし、それらを飲み込む(伝播しない)か、プログラムを終了する必要があります.
  • お客様が操作関数の実行中に投げ出された異常に反応する必要がある場合、クラスは、構造関数ではなく一般的な関数を提供して操作を実行する必要があります.