「Effective C++」item 7:マルチステートベースクラスのvirtual構造関数を宣言


今日は虚関数を研究して、ずっと困っていたいくつかの問題を解決して、特にこの間仕事を探して面接する時はっきり言えないいくつかの問題を探して、とっくにこのように知っていて、どうして当初にしなければなりませんか!
Questions:
(1)なぜ虚関数を使うのか.
(2)virtual構造関数を定義する理由は?
(3)virtual構造関数を定義するのはいつですか.virtual構造関数を定義しないのはいつですか.
Answers:
(1)なぜ虚関数を使うのか.
C++における虚関数の役割は主に実現された
多態のメカニズム.多態については簡単に言えば
親タイプのポインタで子クラスのインスタンスを指す
を選択し、親のポインタで実際の子のメンバー関数を呼び出します.この技術は親のポインタに「多様な形態」を持たせることができ、これは汎用技術である.Javaのマルチステートはinterfaceとabstractによって実現され、javaにはvirtualという言葉はありません!
関数を虚関数として定義します.関数が実装されていない関数ではありません.
彼を虚関数として定義するのは、ベースクラスのポインタでサブクラスを呼び出すこの関数を許可するためである.Javaでは、サブクラスがinterfaceで定義された関数を実装している限り、インタフェース宣言の参照は必ずサブクラスのこの関数を呼び出し、サブクラスが定義されていない場合は、インタフェースの関数を呼び出します.
関数が実装されていないことを表す純粋な虚関数として定義します.彼を定義するのはインタフェースを実現するためで、規範の役割を果たして、規範はこれを継承します.クラスのプログラマはこの関数を実装する必要があります.
まずvirtualを使用しない例を見てみましょう.
このプログラムはまず1つのベースクラスを定義した:TimeKeeper、ベースクラスに実装された関数printがあり、それから1つのクラスAtomicClockを定義してこのTimeKeeperを継承し、TimeKeeperにも1つの関数printが定義され、main関数ではまず1つのベースクラスポインタを定義し、AtomicKeeperクラスのメンバー関数getTimeKeeperによって返されるインスタンスを指す.そしてこのベースクラスポインタでprintを呼び出すと、いったいベースクラスとサブクラスのどちらのprintが呼び出されますか?
// VirtualConstructor.cpp :              。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class TimeKeeper {
public:
	TimeKeeper() {}
	void print()
	{
		cout<<"call print() of TimeKeeper......"<<endl;
	}
	virtual ~TimeKeeper() {}
	virtual TimeKeeper* getTimeKeeper() = 0;
};

class AtomicClock: public TimeKeeper {
public:
	AtomicClock(){}
	~AtomicClock(){}
	TimeKeeper* getTimeKeeper()
	{
		return new AtomicClock();
	}
	void print()
	{
		cout<<"call print() of AtomicClock......"<<endl;
	}
};

int main(int argc, _TCHAR* argv[])
{
	AtomicClock ato;
	TimeKeeper* ptk = ato.getTimeKeeper();

	ptk->print();

	delete ptk;
	return 0;
}
実行結果は、視聴者の予想を裏切る可能性があります.呼び出したベースクラスTimeKeeperのprintメソッド!
このプログラムを変更して、ベースクラスのprint関数をvirtual関数に変更します.
// VirtualConstructor.cpp :              。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class TimeKeeper {
public:
	TimeKeeper() {}
	virtual void print()   // virtual  
	{
		cout<<"call print() of TimeKeeper......"<<endl;
	}
	virtual ~TimeKeeper() {}
	virtual TimeKeeper* getTimeKeeper() = 0;
};

class AtomicClock: public TimeKeeper {
public:
	AtomicClock(){}
	~AtomicClock(){}
	TimeKeeper* getTimeKeeper()
	{
		return new AtomicClock();
	}
	void print()
	{
		cout<<"call print() of AtomicClock......"<<endl;
	}
};

int main(int argc, _TCHAR* argv[])
{
	AtomicClock ato;
	TimeKeeper* ptk = ato.getTimeKeeper();

	ptk->print();

	delete ptk;
	return 0;
}

今回の実行結果はサブクラスのprint関数を呼び出したものです!
これが虚関数の不思議な機能であり、Javaの多態メカニズムとの違いでもある.なぜこのような効果が現れるのかというと、それは虚関数テーブルに関する知識であり、各クラスのインスタンスには本クラスが実現した虚関数リストが格納されており、ベースクラスポインタで指すと、オブジェクトリストの関数が呼び出される.ここでは余計なことは言わない!
(2)virtual構造関数を定義する理由は?
虚関数機能を持つクラスがある場合は、次の理由で虚解析関数が必要です.クラスに虚関数機能がある場合、クラスに継承されるためにベースクラスとしてよく使用されます.      2.ベースクラスの場合、派生クラスはnewを使用して割り当てられます.      3.派生クラスオブジェクトがnewを使用して割り当てられ、ベースクラスを指すポインタによって制御される場合、ベースクラスを指すポインタによって削除されることが多い.C++は、derived classオブジェクトがbase classポインタを介して削除され、そのbase classがnon-virtual構造関数を持っている場合、その結果は不確定になり、通常、ベースクラスの構造関数が呼び出され、派生クラス定義のメンバーが構造されず、メモリ漏洩などの問題が発生することを示している.
ベースクラスにダミー構造関数がある場合,最下層の派生クラスの構造関数が最初に呼び出され,次いで各ベースクラスの構造関数が呼び出される.
次の例を示します.
virtualフィクション関数は適用されません:
// VirtualConstructor.cpp :              。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class TimeKeeper {
public:
	TimeKeeper()
	{
		cout<<"constructor of TimeKeeper......"<<endl;
	}
	~TimeKeeper()
	{
		cout<<"deconstructor of TimeKeeper......"<<endl;
	}
	virtual TimeKeeper* getTimeKeeper() = 0;
};

class AtomicClock: public TimeKeeper {
public:
	AtomicClock()
	{
		cout<<"constructor of AtomicClock......"<<endl;
	}
	~AtomicClock()
	{
		cout<<"deconstructor of AtomicClock......"<<endl;
	}
	TimeKeeper* getTimeKeeper()
	{
		return new AtomicClock();
	}
};

int main(int argc, _TCHAR* argv[])
{
	AtomicClock ato;
	TimeKeeper* ptk = ato.getTimeKeeper();

	delete ptk;
	return 0;
}

実行の結果、ptk解放時にベースクラスTimeKeeperの解析関数のみが呼び出されず、サブクラスの解析関数が呼び出されず、これによりサブクラスの「成分」が解放されず、メモリ漏洩の問題が発生しやすい!実は原因は上と同じで、構造関数もメンバー関数です!
virtual構造関数を使用する例:
// VirtualConstructor.cpp :              。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

class TimeKeeper {
public:
	TimeKeeper()
	{
		cout<<"constructor of TimeKeeper......"<<endl;
	}
	virtual ~TimeKeeper() 
	{
		cout<<"deconstructor of TimeKeeper......"<<endl;
	}
	virtual TimeKeeper* getTimeKeeper() = 0;
};

class AtomicClock: public TimeKeeper {
public:
	AtomicClock()
	{
		cout<<"constructor of AtomicClock......"<<endl;
	}
	~AtomicClock()
	{
		cout<<"deconstructor of AtomicClock......"<<endl;
	}
	TimeKeeper* getTimeKeeper()
	{
		return new AtomicClock();
	}
};

int main(int argc, _TCHAR* argv[])
{
	AtomicClock ato;
	TimeKeeper* ptk = ato.getTimeKeeper();

	delete ptk;
	return 0;
}

今回の実行結果では、サブクラスAtomicKeeperの構造関数を呼び出し、ベースクラスTimeKeeperの構造関数を呼び出し、完了したメモリ解放操作を実現します.これもマルチステートを利用して実現されますが、マルチステートを使用するにはvirtual構造関数を使用する必要があります.
実際,優れたプログラマーはしばしばベースクラスの構造関数を虚関数として定義する.なぜなら、ベースクラスの構造関数を虚関数として定義すると、deleteを使用して派生クラス定義を指すオブジェクトポインタを削除すると、対応するクラスの構造関数(派生クラスとベースクラスを含む)が呼び出されるからです.解析関数を虚関数として定義しない場合は、ベースクラスの解析関数のみが呼び出されます.
コンストラクション関数の呼び出し順序は、常に次のとおりです.1.ベースクラス構築関数.複数のベースクラスがある場合、コンストラクション関数の呼び出し順序は、メンバー初期化テーブルでの順序ではなく、クラス派生テーブルに現れる順序です.        2.メンバークラスオブジェクト構築関数.複数のメンバークラスオブジェクトがある場合、コンストラクション関数の呼び出し順序は、メンバー初期化テーブルに表示される順序ではなく、オブジェクトがクラスで宣言される順序です.        3.派生クラス構築関数.構造関数の呼び出し順序は常に以下の通りである:構造関数の呼び出し順序は構造関数の呼び出し順序と正反対であり、上の3つの点を逆に使えばよい.まず派生クラスの構造関数を呼び出す.次に、メンバークラスオブジェクトの構造関数を呼び出します.最後に、ベースクラスの構造関数を呼び出します.解析関数は、下の3つの場合に呼び出されます:1.オブジェクトのライフサイクルが終了し、破棄された場合(一般クラスメンバーのポインタ変数と参照はiで構造関数を自動的に呼び出さない).        2.deleteがオブジェクトのポインタを指す場合、またはdeleteがオブジェクトのベースクラスタイプポインタを指し、そのベースクラス架空関数が虚関数である場合.        3.オブジェクトiはオブジェクトoのメンバーであり、oの構造関数が呼び出されると、オブジェクトiの構造関数も呼び出される.
(3)virtual構造関数を定義するのはいつですか.virtual構造関数を定義しないのはいつですか.
       1.マルチステート性質のbase classはvirtual構造関数を宣言するべきである.classにvirtual関数がある場合は、virtual構造関数を持つ必要があります.親クラスのポインタから実際の子クラスのメンバー関数を呼び出すには、マルチステート(Multiple State)の場合にのみ使用できます.       2.classの設計目的はbase classとして使用されないか、多態性を備えていない場合はvirtual構造関数を宣言すべきではない.