Effective C++ Section5-8 第5,6章藤川、第7,8章竹内 参考書の用語のルール オブジェクト ユーザ定義だけでなく、組み込み型(C++にもとからある型)の ものでもオブジェクトと呼ぶ シグネチャ 2 引数の型と戻り値の型のこと。C++における公式な用語では 戻り値は含まないが、この本では含めて呼ぶ。 単語の意味 extern 複数ソースコードをまたいで変数やメソッドを使用できるようにする 宣言。 deque デックと読む。vectorと似ており、動的配列の前後に要素の挿入や 削除が可能。STLの一種。 STL Standard Templete Library。データを格納するコンポーネントとその コンポーネントを操作するためのアルゴリズム群であり、C++の標 準機能。dequeのほかには、vector、list、queue、stackなどがある。 explicit 3 コンストラクタが暗黙の型変換を行わないようにするための宣言。 特に理由がなければexplicit付きでコンストラクタを宣言したほうが 安全。 第5項(1) C++が自動で書き、自動で呼び出す関数を知ろう コンストラクタ、デストラクタ、コピー代入演算子 一つもコンストラクタを宣言しなかった場合 コンパイラが自動でデフォルトコンストラクタを生成 コピーコンストラクタ、デストラクタ、コピー代入演算子を宣言しな かった場合 コンパイラが自動でそれらを生成 自動生成された関数はすべてpublicでinlineとなる class Empty{};と書いても… class Empty{ public: Empty(){…} //デフォルトコンストラクタ Empty(const Empty& rhs){} //コピーコンストラクタ ~Empty(){…} //デストラクタ Empty& operator=(const Empty& ths){…} //コピー代入演算子 }; となる 4 第5項(2) C++が自動で書き、自動で呼び出す関数を知ろう コンストラクタ、デストラクタ、コピー代入演算子 関数の仮想性は継承される 基底クラスが仮想デストラクタを持てば、派生クラスにコンパ イラが生成するデストラクタも仮想となる 基底クラスのデストラクタが仮想でない場合には、コンパイラ が生成するデストラクタは仮想にはならない コンストラクタが明示的に宣言された場合には、コンパイ ラはデフォルトコンストラクタを生成しない 5 引数をとるコンストラクタを持つクラスを設計した場合には、引 数をとらないコンストラクタ(デフォルトコンストラクタ)を生成し ない 第5項(3) C++が自動で書き、自動で呼び出す関数を知ろう コンストラクタ、デストラクタ、コピー代入演算子 コピー代入演算子の自動生成によって、C++の規則を破 りかねないようなコードの場合には、コンパイラは生成し ない e.g.) 6 メンバに参照変数を持ち、コード中で参照するものを変更してしまう ような演算がある場合 constなデータメンバを持つ場合 コピー代入演算子をprivateにしているクラスの派生クラスである場合 (12項) 第6項(1) コンパイラが自動生成することを望まない関数は、使用を禁止しよう コンストラクタ、デストラクタ、コピー代入演算子 コピーコンストラクタとコピー代入演算子を禁止し、コン パイルできないようにしたい場合 普通の関数であれば宣言しなければよい コピーコンストラクタとコピー代入演算子を宣言しなければ、コ ンパイラが自動生成してしまう コンパイラが生成する関数はすべてpublicである コピーコンストラクタとコピー代入演算子をprivateに記述する ことで、コンパイラが自動生成することはなくなる まだ安全ではない 意図的に定義を書かなければ、これらの関数を呼び出してしまう コードを書いてもコンパイル時のリンカエラーになるだけ 7 ほかのメンバ関数やフレンド関数はprivateでも呼び出せる iostreamなどの実装にも使われており、有名な手法である 第6項(2) コンパイラが自動生成することを望まない関数は、使用を禁止しよう コンストラクタ、デストラクタ、コピー代入演算子 リンカエラーをコンパイラエラーにすることもできる メリット:エラーが早くわかる 方法:コピーコンストラクタとコピー代入演算子をprivateに宣言した クラスを作り、このクラスを継承する e.g. 8 class Uncopyable{ protected: Uncopyable(){} //生成と廃棄は許可 ~Uncopyable(){} private: //コピーと代入は禁止 Uncopyable(const Uncopyable&); Uncopyable& operator=(const Uncopyable&); }; class HomeForSale: pricate Uncopyable{ ... }; 第6項(3) コンパイラが自動生成することを望まない関数は、使用を禁止しよう コンストラクタ、デストラクタ、コピー代入演算子 Uncopyableの継承はpublicでなくてよい Uncopyableのデストラクタは仮想でなくてよい Uncopyableはデータを持っていないため、仮想でないデストラ クタは「空の基底クラスの最適化」のため本来は望ましい ところがUncopyableクラスは基底クラスとして設計されており、 多重継承された場合には「空のクラスの最適化」が適用され ない可能性がある Boostライブラリには、この働きをするnoncopyableという クラスがある 9 第7項(1) ポリモーフィズムのための基底クラス には仮想デストラクタを宣言しよう 時間を記録するには、いろいろな方法がある TimeKeeperというクラスを定義し、いろいろな時間記録クラスをその派生クラス にしてみよう。 TimeKeeper AtomicClock WaterClock WristWatch これら派生クラスのクライアントは、時間の記録方法や計算方法の詳細を知らず に、クラスの機能を使いたいと考える。 ファクトリ関数が便利(???なんで?) ファクトリ関数とは、派生クラスのオブジェクトを生成し、それを指し示す基底ク ラスのポインタを返す関数。 第7項(2) ポリモーフィズムのための基底クラス には仮想デストラクタを宣言しよう 基底クラスのポインタptkを使って、派生クラスのオブジェクトを操作する ptkをdeleteすると、どうなるか? 回避するためには、基底クラスに仮想デストラクタを与える。 基底クラスTimeKeeperのデストラクタが呼び出され、その部分だけが破棄される AtomicClockのデストラクタは呼び出されず、AtomicClockにあるデータメンバは破 棄されない AtomicClockオブジェクトのパーツが残る AtomicClockのデストラクタが呼び出され、オブジェクト全体が破棄されるようになる。 TimeKeeperのような基底クラスは、一般にデストラクタ以外にも仮想関数を 持っているはず 仮想関数を持つクラスは、ほぼ間違いなく仮想デストラクタを付けるべき 第7項(3) ポリモーフィズムのための基底クラス には仮想デストラクタを宣言しよう 逆に、仮想関数のないクラスは、基底クラスとして使われるために作られたのでは ないかもしれない 32bitのintのメンバ変数を2つ持つクラスPointを考える Pointのオブジェクトは、64bitレジスタに収まる。 もし、Pointが仮想デストラクタを持つなら 仮想関数を持つクラスは、仮想テーブルポインタと呼ばれる「オーバーライトされた仮想関数のうち実際どれを実 行するか」の情報を持つ必要がある。 その分オブジェクトが大きくなってしまう。(ポインタは32, 64bit) 仮想デストラクタのせいで、オブジェクトサイズが50~100%増える! 基底クラスとして使うつもりがなければ、仮想デストラクタを持つべきでない StringやVectorなどのクラスは、仮想デストラクタを持っていない これらの派生クラスを作るのは危険。(そもそも基底クラスとして使われると想定されていない) まとめ ポリモーフィズムのための基底クラスには仮想デストラクタを宣言しよう。特に、クラスが仮想関数を持つなら、 仮想デストラクタも持たせよう。 基底クラスとしてデザインされていないクラスには、仮想デストラクタを宣言すべきでない 第8項(1) デストラクタから例外を投げないよう にしよう デストラクタから例外を投げることは、禁じられてはい ないけど間違いなく危険 クラスWidgetのオブジェクトを10個持つVector vを考える。 Vが破棄される時、Widgetも順に破棄される。 Widgetのデストラクタに例外を投げる処理があった場合、 同時に複数のアクティブな例外が存在する状態がありう る その場合の振る舞いは未定 では、デストラクタが”例外を投げるかもしれない処理” をしなければいけない場合、どうすればよいか? . 第8項(2) デストラクタから例外を投げないよう にしよう では、デストラクタが”例外を投げるかもしれない処理” をしなければいけない場合、どうすればよいか? データベースへの接続のクラスDBConnectionを考える DBConnectionオブジェクトを管理するクラスDBConnが 存在する 接続を切る関数void close()を持ち、失敗時は例外を投げるこ とにする メンバとしてDBConnectionのオブジェクトdbを持つ デストラクタ内ではdb.close()を呼び出し、接続の切り忘れを防 ぐ DBConnオブジェクトが破棄されると、デストラクタでデー タベースへの接続を切ろうとする db.close()に失敗し例外が投げられると、DBConnのデストラク タは例外を投げ、処理が中断されてしまう 第8項(3) デストラクタから例外を投げないよう にしよう 2つの対処法 DBConnデストラクタ中でプログラムを中止してし まう db.close()の例外を検出(try-catch文)し、それを記録 してstd::abort() それ以降処理を続けることに意味がなければ有効 DBConnデストラクタが例外を飲み込んでしまう db.close()の例外を検出し、それを記録するだけ 一般には良くないが、プログラムの中止や未定義な 振る舞いを防ぐためには有効 第8項(4) デストラクタから例外を投げないよう にしよう どちらも素晴らしい方法ではない より良いのは、DBConnのクライアント(利用者)に”問題に対処する機 会”を与えるようなデザイン たとえば、DBConn自身が、接続を切る関数closeを提供する db.close()を呼ぶ責任を、DBConnのデストラクタから、DBConnのクライアン トに移す クライアントがcloseを呼び出せるようにするという事は、クライアントにエラー 処理の機会を与えることを意味する 例外はデストラクタ以外の関数から投げられるべき クライアントの手で接続が切断されないままDBConnが破棄される場合は、 やむを得ずデストラクタ内でdb.close()を呼び出す(バックアップ) 中止されようが飲みこまれようが、クライアントには文句を言う筋合いはない まとめ デストラクタは例外を投げてはいけない。デストラクタ内で例外を投げ る関数を呼び出す場合、その例外を補足し、処理(中止or飲みこみ)す るようにする
© Copyright 2024 ExpyDoc