Effective C++学習ノートの第4章(2)

4925 ワード

chapter 4設計と宣言
item 19:設計classは設計typeのように実際には、各クラスを設計するには以下のいくつかの問題に直面する必要があります.soは、新しいタイプを定義する前に、これらの問題をはっきり考えたほうがいいです.1)新しいクラスがどのように作成および破棄されるか.これは、クラスのコンストラクション関数とコンストラクション関数、およびメモリを申請し、メモリを解放する関数に関連します.たとえばnewとdelete operator 2)オブジェクトの初期化Initializationとオブジェクトの付与assignmentにはどのような違いがありますか.この問題は,コンストラクション関数と付与オペレータの違いを決定する.初期化と付与を混同しないでください.彼らは異なる関数によって実現されているからです.詳細はitem 4.3)新しいオブジェクトが値で渡された場合(pass by value)、何を意味しますか.コピーコンストラクション関数は、値伝達がどのように実現されるかを決定するタイプであるためです.4)新しいタイプの合法的な制限とは何か.クラスのメンバー変数では、通常、いくつかの値の組合せだけが有効です.これらの組み合わせは、あなたのクラスが維持しなければならない不変量を決定します.これらの不変量は、メンバー関数でエラーチェックをしなければならないことを決定します.特に、コンストラクション関数では、関数とsetter関数をコピーします.これは、関数放出異常、および関数異常詳細列(exception specifications)に影響する可能性があります(使用されることは少なく、私にもわかりません).5)新しいクラスは継承図系(inheritance graph)に協力する必要がありますか?クラスが既存のクラスを継承している場合は、既存のクラスの設計に制限されます.特に、それらの関数が虚関数であるか、虚関数ではありません.他のクラスにクラスを継承させることを許可したい場合は、定義した虚関数が虚関数、特に構造関数であるべきかどうかを決定します.虚関数として定義したほうがいいです.そうしないと、部分的な構造になります.詳細はitem 7を参照してください.6)クラスにはどのようなタイプの変換が必要ですか.あなたのクラスはタイプの海に存在するので、あなたのクラスは他のクラスと転化する行為があるべきですか.クラスがタイプT 1を暗黙的にタイプT 2に変換することを許可する場合は、T 1にタイプ変換関数(operator T 2など)を書くか、T 2にnon-explicit-one-argument構造関数を書く必要があります.明示的な変換のみを望む場合は、この変換を実現するために関数を書かなければなりません.タイプ変換オペレータ(type conversion operators)やnon-explicit-one-argumentの関数を構築することはできません.詳細はitem 15を参照してください.7)どのようなオペレータや関数が新しいタイプにとって意味があるか.これにより、クラスにどの関数を宣言する必要があるかが決まります.メンバー関数の中には、必ずしもそうではありません.詳しくはitem 23,24,46を参照してください.8)どのような標準関数が許されないか.これは、privateとして宣言すべき関数を説明します.詳細はitem 6を参照してください.9)新しいタイプのメンバーへのアクセス権を持っている人.この問題は、publicとして宣言すべき変数、protectとして宣言すべき変数、privateを決定するのに役立ちます.これにより、どのクラスや関数が友元になるか、あるいは別のクラスにネストされるかが合理的かどうかも決まります.10)あなたの新しいタイプの未宣言インタフェース(undeclared interface)は何ですか.効率、異常なセキュリティ(item 29を参照)、リソース運用(マルチタスクロックとダイナミックメモリ)にどのような保証を提供しますか?あなたがこの方面で提供した保証は、あなたのクラスのコード実装に相応の制限条件を加えます.11)あなたの新しいタイプがどれほど一般化されているか.新しいタイプを本当に定義していないかもしれません.タイプファミリー全体を定義しただけです.新しいタイプを定義したくない場合は、新しいタイプテンプレートを定義します.12)本当に新しいタイプが必要ですか?派生クラスを定義した場合は、既存のクラスにいくつかの機能を追加したいだけです.非メンバー変数やテンプレートを定義するだけで目標を達成できます.
item 20:pass-by-reference-to-constでpass-by-value 1)pass-by-valueを置き換えるパラメータが関数に入ると、関数は自動的にパラメータをcopyします.これはパラメータを渡す必要があるときにパラメータの構造関数を呼び出し、関数が戻るときに対応する構造関数を呼び出します.呼び出し側も関数の戻り値の複素数である.たとえばpass-by-valueの場合、関数はStudentとPersonのcopyコンストラクション関数のコンストラクション関数を1回呼び出す必要があり、stringを呼び出すcopyコンストラクション関数は4回もコンストラクション関数を呼び出す必要があります.これは私たちが望んでいるものではないに違いないが、pass-by-reference-to-constにはこの問題はなく、オブジェクトが信頼できる初期化と破棄されることを保証することもできる.新しいオブジェクトが作成されていないため、コンストラクション関数とコンストラクション関数は呼び出されません.PS:このconstはここでとても重要です.pass-by-valueであれば,ユーザはパラメータの値を変更できないことを知っており,あってもそのコピーを変更するだけである.referenceは簡単に変更できるので、constと宣言すると関数で変更される心配はありません.2)pass-by-referenceはまた、オブジェクトカット(slicing)の問題を回避することができる.派生クラスオブジェクトがベースクラスオブジェクトとして渡されると、ベースクラスのcopyコンストラクション関数が呼び出され、派生クラスのいくつかの特有の性質が切り取られ、構築されたのはベースクラスのオブジェクトにすぎません.たとえば、
class Window {
public:
  ...
  std::string name() const;           // return name of window
  virtual void display() const;       // draw window and contents
};

class WindowWithScrollBars: public Window {
public:
 ...
  virtual void display() const;
};

ウィンドウのnameを印刷してウィンドウを表示する関数を書く必要がある場合は、次の書き方が間違っています.
void printNameAndDisplay(Window w)         // incorrect! parameter
{                                          // may be sliced!
  std::cout << w.name();
  w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

printNameAndDisplyを主に呼び出すとどうなりますか?注意displayは虚関数であり、pass-by-valueであればwindowのcopyコンストラクション関数を呼び出し、windowのオブジェクトを生成するだけなので、windowWithScrollBars::displayではなくwindow::displayのみが呼び出されます.
void printNameAndDisplay(const Window& w)   // fine, parameter won't
{                                           // be sliced
  std::cout << w.name();
  w.display();
}

しかし,引用伝参であれば上記の問題は起こらない.PS 1:built-in typeの場合、pass-by-valueを選択するとより効率的になります.これはSTLのiteratorと関数オブジェクトにも適用されます.pass-by-valueとして設計されているからです.PS 2:一般的なbuilt-in typeは非常に小さいので、それは小さなタイプではないでしょうか.ユーザーが自分で定義したものさえpass-by-valueに適しているのではないでしょうか.これは信頼できない結論だ.オブジェクトが小さいため、呼び出されたcopyコンストラクション関数unexpensiveを説明することはできません.STLコンテナを含む多くのオブジェクトは、ポインタよりも多く含まれていますが、very expensiveのものをすべてコピーする必要があります.inexpensiveのcopyコンストラクション関数のある小さなオブジェクトでも、効率の問題がある可能性があります.一部のコンパイラではbuilt-in typeとユーザー定義typeは異なり、下位の記述が同じであっても異なります.例えば、一部のコンパイラは、1つのdoubleからなるオブジェクトだけをバッファに入れることを拒否していますが、正規の基礎の上で裸のdoublesに対して喜んでいます.この場合、コンパイラは当然ポインタ(referencesの実装体)をバッファ内に入れるので、by referenceの方法で参照を変更する.PS 3:別の小さなユーザー定義のタイプがpass-by-valueに合わないのは、その大きさが変化しやすいためです.現在の小さなタイプは、内部実装が変わる可能性があるため、将来膨大になる可能性があります.別のC++コンパイラを変更してもtypeのサイズを変更する可能性があります.例えば、いくつかの標準的なライブラリ実装バージョンのstringタイプは、他のバージョンの7倍ほど大きい.まとめるとbuilt-in typeとSTLのiteratorと関数オブジェクトだけがpass-by-valueを使用できます.pass-by-valueの場合inexpensiveを合理的に仮定できるからです.他のタイプはpass-by-reference-to-constが優先されます.より効率的であり、切断(slicing)の問題を回避できるからです.