C++学習ノート(五)類雑談


constメンバー関数
constメンバー関数の存在価値は主にconstオブジェクトにある.コンパイラはconstオブジェクトが変更できないことを知っています.constオブジェクトが変更できないことを保証するために、constオブジェクトはconst修飾のメンバー関数しか呼び出せないことを規定します.この関数を呼び出すとオブジェクトの状態が変更されないことを保証するために、クラスのメンバー関数がチェックされます.
Constオブジェクトはconstメンバー関数のみを呼び出すことができ、constオブジェクトではなくすべてのメンバー関数を呼び出すことができますが、constオブジェクトにconst以外のバージョンがあることを望む場合もあります.たとえば、アキュムレータクラスAccuを定義します.
class Accu {
	public:
		Accu():data(0){}
		Accu& add(int d){data += d; return *this;}
		const Accu&show() const{cout<<data; return *this;};
	private:
		int data;
}

show関数は結果を表示するだけなのでconstメンバー関数であることがわかります.チェーンプログラミングスタイルというプログラミングスタイルがあります.上のクラスでは、コードを次のように書くことができます.
Accu ac;
ac.add(1).add(30).show();

各関数はオブジェクト自体の参照を返しているので、チェーンスタイルでプログラムを呼び出すことができます.このスタイルのメリットは言いません.今、主にその問題を説明します.注意深い子供靴では、show関数は最後に呼び出されるしかないことがわかります.constメンバー関数であり、オブジェクトへのconst参照を返します.つまり、次のような書き方がコンパイルエラーになります.
ac.add(1).add(30).show().add(6);

constオブジェクトでconst以外のメンバー関数を呼び出すことはできません.この問題を解決するには、show関数を再ロードし、const以外のバージョンを追加する必要があります.
Accu&show() {cout<<data; return *this;};

これで問題は解決した.この中には、非constメンバー関数がconstメンバー関数よりも優先度が高いという関数マッチング優先度が存在します.したがって、リロード後、constオブジェクト以外で呼び出されるshow関数は、constオブジェクトのみがconstバージョン以外のshow関数を呼び出すに違いありません.
mutableデータメンバー
実際にはconstオブジェクトでもメンバー変数を変更できるようにする必要がある場合があります.この場合、mutableキーワードでメンバーを修飾することができ、constメンバー関数からでもメンバーの値を変更することができます.
class役割ドメイン
クラスのメンバー関数の内部では、クラスで定義されたメンバー変数やタイプなどを直接参照することができ、そのメンバー関数がクラスの外部に定義されていても、実際にはパラメータリストで直接参照することができ、戻り値タイプを除くことがわかります.なぜなら、コンパイルは、処理時に関数名に遭遇した場合にのみその役割ドメインを決定します.つまり、関数名の後に定義されたすべてのクラスのメンバーを直接参照することができ、その前に定義された戻り値は自然に除外されます.次の例を見てください.
class A {
	public:
		typedef int byte32;
		byte32 test(byte32 a);
};
A::byte32 A::test(byte32 a) {
//some code
}

byte 32はクラスAの内部に定義されたタイプで、戻り値がclassの役割ドメインの外にあるため、A:修飾を加える必要がありますが、パラメータリストは関数名の後に定義されているので、classの役割ドメイン内であるため、A:修飾を加える必要はありません.ここでは、C++の名前検索メカニズムについて、以下の定義を考えてみましょう.
typedef string Type;
Type initVal();
class Exercise {
public:
// ...
Type test();
typedef double Type;
Type setVal(Type);
Type initVal();
private:
int val;
};
Type Exercise::setVal(Type parm) {
val = parm + initVal();
}

この中のタイプがそれぞれどこで定義されているかはっきり言えますか?
C++の名前検索メカニズムは、タイプ名を検索する場合は、まずそのタイプが使用する関数(またはblock)で検索し、見つからない場合は、そのクラスの関数定義の前の部分で検索し、まだ見つからない場合は、クラスの定義の前に検索します(メンバー関数がクラス外に定義されている場合は、そのメンバー関数の定義の前に検索します).変数名または関数名が検索される場合、class役割ドメインが検索されると、変数の使用前の部分だけでなくclass全体が検索されるという違いがあります.
そこで、コードを振り返ってみると、classの上のType initVal()の戻りタイプは自然にどのグローバルTypeなのか、クラスのType initVal()のTypeは何なのか.このタイプは関数内部では使用されないため、関数の役割ドメインを検索するステップを省いて、クラスの役割ドメインを直接検索し、クラスで定義されたタイプを見つけます.setVal関数の宣言はこれと同じで、クラスで定義されたTypeです.肝心なのはその定義です.メンバー関数のパラメータリストはclassの役割ドメインにあるので、パラメータリストのTypeはクラスで定義されたTypeですが、戻り値は異なります.それはそのグローバルTypeです.この関数の内部を見て、変数parmについて、コンパイラはまず関数の内部を検索して、パラメータリストのparmaを発見して、それです.しかしvalとinitValは関数内部で宣言されていないため、次のステップはclass役割ドメイン全体を検索し、宣言位置を発見します.クラスにはType test()を宣言する関数もあります.クラス内のTypeの定義の前に宣言が置かれているため、クラス役割ドメインでのタイプ名の検索時に使用する場所の前の部分だけをコンパイルすると、ここではTypeの定義が見つからないため、コンパイラはクラス定義以外の環境を検索し、グローバルなType定義を見つけます.うん、ちょっと散らかっているようですが、ここです.本当はこの部分を書きたくなかったのですが、実際の開発ではネーミングの重複はできるだけ避けなければならないので、この部分は実際には理論的意義が実践的意義よりも大きいので、メモの完備のために書きましたが、よく見えなければそのままスキップしましょう.
初期化リスト
クラスのコンストラクション関数では、C++がクラスメンバーを初期化するメカニズムを提供しています.これが初期化リストです.次のコンストラクション関数の2つの定義を考慮します.
A(B& pb) {
	b = pb;
}

A(B& pb): b(pb) {
}

2つ目の方法では、コロンの後ろにあるのは初期化リストです.この部分を省略すると、コンパイラはデフォルトのコンストラクション関数を使用してクラスメンバーごとに初期化文を生成するので、1つ目の方法は次のようになります.
A(B& pb):b() {
	b = pb;
}

この2つの方式を考慮すると、タイプBがオリジナルデータ型であれば、両者の間には実際には何の違いもないが、クラス型であれば、両者の間の性能オーバーヘッドの問題を考慮しなければならない.第1の方式はまずデフォルトの構造関数を呼び出し、それから賦値を行い、第2の方式はコピー構造関数を直接呼び出す.この2つのコンストラクション関数は、次のように定義されているとします.
B():C(0) {
}

B(B& b):C(b.c) {
}

この場合、デフォルトのコンストラクション関数とコピーコンストラクション関数のパフォーマンスは同じであることがわかりますが、最初の方法は割り当てられたオーバーヘッドが多くなります.この方法では、パフォーマンスオーバーヘッドの違いが十分でない場合は、次の状況を考慮します.
A(C& c):b() {
	b = B(c);
}

A(C& c):b(c) {
}

今回は、まずデフォルトのコンストラクション関数を呼び出し、次に非デフォルトのコンストラクション関数を呼び出し、次に=操作を呼び出します(ここでは再ロードできます).2つ目の方法は、非デフォルトのコンストラクション関数を1回呼び出すだけでいいです.
したがって,いくつかの場合,初期化リストを用いることと構造関数体で初期化を行うことで,それらの性能にはあまり差はないが,別の場合,初期化リストを用いることでより効率的になる.これは効率的に言えば、実際には、メンバー変数がデフォルトのコンストラクション関数を提供していない場合など、初期化リストを使用する必要があります.したがって、両方が代替されている場合、初期化リストはより優れた選択になりがちですが、これは強制的な要求ではなく、提案にすぎません.具体的な状況は、ビジネスロジックによって異なります.