ファクトリ関数(仮想コンストラクタ)の実装方法を教えてください – プログラミング – Home

ファクトリ関数(仮想コンストラクタ)の...
 
通知
すべてクリア

[解決済] ファクトリ関数(仮想コンストラクタ)の実装方法を教えてください

固定ページ 1 / 2

鍋奉行
 鍋奉行
(@鍋奉行)
ゲスト
結合: 17年前
投稿: 15
Topic starter  

お世話になります。
Effective C++ 第3版 第7項 ポリモーフィズムのための基底クラスには仮想デストラ
クタを定義しよう
という項目があります。

例として、TimeKeeperという時間を記録するクラスを、基底クラスを定義し、いろいろな
「時間記録クラス」を派生させていますが、メイヤーさんの偉大なる手抜き(そんなの分
かって当然テカww)でサンプルソースの骨子しか書かれていません、注釈でも
「実際には生成するオブジェクトの型(AtomicClockにするか、WaterClockにするかな
ど)を決めなければなりません。簡単な方法としてはgetTimekeeperが引数を取るように
し、その引数によって生成するオブジェクトの型を決める方法などが考えられます。しか
し、ここでは、そのような詳細は省略しているのです。
とのくだりがあります、この項目でもそうなのですが、この本には頻繁にファクトリ関数
(仮想コンストラクタ)という文言が出てきます。いろいろ調べた結果
自らのコピーコンストラクタによってオブジェクトを動的に生成するメンバ関数というこ
とらしいのですが、メイヤーさんの骨子に詳細を付加してどういうものなのか、書いて頂
けないでしょうか。
識者の方宜しくお願い致します。

class Timekeeper {
public:
Timekeeper();
virtual ~Timekeeper();
};

void Timekeeper::Timekeeper()
{
}

class AtomicClock : public Timekeeper {
};

class WaterClock : public Timekeeper {
};

class WristWatch : public Timekeeper {
};

Timekeeper* getTimekeeper();
Timekeeper* ptk = getTimekeeper();
delete ptk;

VS2005/MFC


引用未解決
トピックタグ
επιστημη
 επιστημη
(@επιστημη)
ゲスト
結合: 22年前
投稿: 1301
 

> 自らのコピーコンストラクタによってオブジェクトを動的に生成する...

よーわからん。

TimeKeeper* getTimeKeeper(int id) {
switch ( id ) {
case 0: return new AtomicClock();
case 1: return new WaterClock();
case 2: return new WristWatch();
}
return 0;
}

じゃダメなの?


返信引用
鍋奉行
 鍋奉行
(@鍋奉行)
ゲスト
結合: 17年前
投稿: 15
Topic starter  

επιστημη さんお世話になります。
>よーわからん。
すいません、質問者の理解が足らなくて
ポインターのコレクションの代入演算子
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200412/04120026.txt
のスレでmonkeyさんの説明から引用させて頂きました。
それから、先に書いたメイヤーさんのスケルトンに若干補足と訂正をさせて頂きます。

class Timekeeper {
public:
Timekeeper();
virtual ~Timekeeper();
};

class AtomicClock : public Timekeeper {
};

class WaterClock : public Timekeeper {
};

class WristWatch : public Timekeeper {
};

Timekeeper* getTimekeeper(); // TimeKeeperの適当な派生クラスの
オブジェクトを

// 動的に生成し、そのオブジェクトを指し示す

// ポインタを戻す
Timekeeper* ptk = getTimekeeper(); // TimeKeeperの適当な派生クラスの

// オブジェクトを動的に生成する

// それを使う
delete ptk; // オ
ブジェクトを破棄し、メモリを開放する


返信引用
επιστημη
 επιστημη
(@επιστημη)
ゲスト
結合: 22年前
投稿: 1301
 

で、何がやりたいんです?
TimeKeeperから導出されるいくつかのクラスのうち、
どれを選ぶかをどうやってgetTimekeeper() に教えてあげるんでしょうか?


返信引用
鍋奉行
 鍋奉行
(@鍋奉行)
ゲスト
結合: 17年前
投稿: 15
Topic starter  

お世話になります、質問のしかたが大変不味くてごめんなさい。
本には下記の通りに書いてあります、このような条件で著者の言うところのファクトリ関
数、(getTimekeeper関数)はどういう具合に書いたらよいのでしょうか?

class Timekeeper {
public:
Timekeeper();
virtual ~Timekeeper();
};

class AtomicClock : public Timekeeper {
};

class WaterClock : public Timekeeper {
};

class WristWatch : public Timekeeper {
};

ファクトリ関数とは、派生クラスのオブジェクトを生成し、その基底クラスのポインタを
戻す関数です、そのようなファクトリ関数を上のようなTimeKeeperとその派生クラスに書
いておくのです。

Timekeeper* getTimekeeper(); // TimeKeeperの適当な派生クラスのオブジェクトを
// 動的に生成し、そのオブジェクトを指し示す
// ポインタを戻す

ファクトリ関数の一般論にしたがって、「getTimekeeperの戻すポインタ」が指し示すオ
ブジェクトはヒープ上に生成することにします。すると、そのオブジェクトをきちんと破
棄することが重要になります。

Timekeeper* ptk = getTimekeeper(); // TimeKeeperの適当な派生クラス
の // オブジェクトを動的に生成する
... // それを使う
delete ptk; // オブジェクトを破棄し、メモリを開放する

以上です。

また、下記はなにも考えずに派生クラス(AtomicClock)を呼んだ場合なのですが、ファ
クトリ関数を使った場合との違いはあるのでしょうか?
これを実行しますと
基底クラスのコンストラクタ
派生クラスのコンストラクタ
基底クラスのデスクトラクタ
派生クラスのデスクトラクタ
と実行されます。

#include <iostream>

class Timekeeper {
public:
Timekeeper();
virtual ~Timekeeper();
};

Timekeeper::Timekeeper() {
std::cout << Timekeeper running..... << std::endl;
}

Timekeeper::~Timekeeper() {
std::cout << ~Timekeeper running..... << std::endl;
}

class AtomicClock : public Timekeeper {
public:
AtomicClock();
~AtomicClock();
};

AtomicClock::AtomicClock(){
std::cout << AtomicClock running..... << std::endl;
}

AtomicClock::~AtomicClock() {
std::cout << ~AtomicClock running..... << std::endl;
}

class WaterClock : public Timekeeper {
public:
WaterClock();
~WaterClock();
};

WaterClock::WaterClock(){
std::cout << WaterClock running..... << std::endl;
}

WaterClock::~WaterClock() {
std::cout << ~WaterClock running..... << std::endl;
}

class WristWatch : public Timekeeper {
public:
WristWatch();
~WristWatch();
};

WristWatch::WristWatch(){
std::cout << WristWatch running..... << std::endl;
}

WristWatch::~WristWatch() {
std::cout << ~WristWatch running..... << std::endl;
}

int main()
{
AtomicClock atom;
return 0;
}


返信引用
たまに書き込んでみた人
 たまに書き込んでみた人
(@たまに書き込んでみた人)
ゲスト
結合: 17年前
投稿: 4
 

その例だと getTimekeeper 出てきてないし・・・
#getTimekeeperはいずこへ?

>ファクトリ関数とは、派生クラスのオブジェクトを生成し、その基底クラスのポインタを
>戻す関数です、そのようなファクトリ関数を上のようなTimeKeeperとその派生クラスに書
>いておくのです。

Effective C++ が今手元にないので元ネタがよくわからいけど、自分自身を生成するよう
なメンバ関数を書いておくんじゃないのかな?たとえば・・・

class AtomicClock : public Timekeeper {
public:
AtomicClock();
~AtomicClock();

static Timekeeper* getTimekeeper() // <- これじゃだめかしら?
{
return new AtomicClock();
}
};

// で、こんな風にして使う
Timekeeper* ptk = AtomicClock::getTimekeeper();

delete ptk;

なんか例に近いんでは?
ちなみに、普通このままでは使わないけどね(ちょっと細工が必要)。
違ってたら、無視してくださいませ。ではでは・・・


返信引用
tetrapod
 tetrapod
(@tetrapod)
ゲスト
結合: 21年前
投稿: 830
 

> 基底クラスのデスクトラクタ
> 派生クラスのデスクトラクタ
デストラクタ、だよな。
そんなことはありえない。あったらコンパイラのバグだ。
派生クラスのデストラクタ→基底クラスのデストラクタの順のはずだぞ。

> どういう具合に書いたらよいのでしょうか?
もう答えは出てるわけだが (2007/11/16(金) 23:39:38) 納得できないのかな?
> TimeKeeperの適当な派生クラスのオブジェクトを
この「適当な」というのを日本語としてどう解釈するかだけの問題だと思うよ。

テケトー=毎回ランダムであるなら、乱数を生成して選べばいい
適当=設定ファイルにしたがって、なら設定ファイルの内容によって選べばいい

Timekeeper* getTimekeeper() {
switch (rand()%3) {
case 0: return new AtomicClock;
case 1: return new WaterClock;
case 2: return new WristWatch;
}
// NOTREACHED
return 0;
}


返信引用
鍋奉行
 鍋奉行
(@鍋奉行)
ゲスト
結合: 17年前
投稿: 15
Topic starter  

お世話になります
>>たまに書き込んでみた人さん
>>なんか例に近いんでは?
多分本に著者が書きたかったコードだと思います、著者メイヤーさんがこの単元で述べた
かった一番のポイントは
「ポリモーフィズムのための基底クラスには仮想デスクトラクタを宣言しよう。特に、ク
ラスが仮想関数を持つなら、仮想デスクトラクタも持たせよう。」なのです。
しかるに、検証の意味で、たまに書き込んでみた人さんの、書いてくれたソースを元に
基底クラスのデスクトラクタからオーバライドを外すと、著者の意図通り「奇妙な部分的
に破壊されたオブジェクト」が確認できましたww。

>>tetrapodさん
派生クラスのデストラクタ→基底クラスのデストラクタの順のはずだぞ。
おっしゃる通りです私が間違えました。
>>もう答えは出てるわけだが (2007/11/16(金) 23:39:38) 納得できないのかな?
下記のように書きますとerror C2601が出るのですが?
int main()
{
// ↓ここでerror C2601:ローカル関数の定義が正しくありません。
Timekeeper* getTimekeeper() {
switch (rand()%3) {
case 0:
return new AtomicClock;
break;
case 1:
return new WaterClock;
break;
case 2:
return new WristWatch;
break;
}
// NOTREACHED
}
return 0;
}

そんな訳で、この様に書くことで、ファクトリ関数(仮想コンストラクタ)のイメージ
と、コンストラクタ、デスクトラクタの動作が掴めた感じです。

#include <iostream>

class Timekeeper {
public:
Timekeeper();
// ○正しく基底、派生クラスのコンストラクタ、デスクトラクタが実行される
virtual ~Timekeeper();
// ×派生クラスのデスクトラクタが実行されない
// ~Timekeeper();
};

Timekeeper::Timekeeper() {
std::cout << Timekeeper running..... << std::endl;
}

Timekeeper::~Timekeeper() {
std::cout << ~Timekeeper running..... << std::endl;
}

class AtomicClock : public Timekeeper {
public:
AtomicClock();
~AtomicClock();

static Timekeeper* getTimekeeper()
{
return new AtomicClock();
}
};

AtomicClock::AtomicClock(){
std::cout << AtomicClock running..... << std::endl;
}

AtomicClock::~AtomicClock() {
std::cout << ~AtomicClock running..... << std::endl;
}

class WaterClock : public Timekeeper {
public:
WaterClock();
~WaterClock();
static Timekeeper* getTimekeeper()
{
return new WaterClock();
}
};

WaterClock::WaterClock(){
std::cout << WaterClock running..... << std::endl;
}

WaterClock::~WaterClock() {
std::cout << ~WaterClock running..... << std::endl;
}

class WristWatch : public Timekeeper {
public:
WristWatch();
~WristWatch();
static Timekeeper* getTimekeeper()
{
return new WristWatch();
}
};

WristWatch::WristWatch(){
std::cout << WristWatch running..... << std::endl;
}

WristWatch::~WristWatch() {
std::cout << ~WristWatch running..... << std::endl;
}

int main()
{
std::cout << 1:AtomicClock 2:WaterClock 3:WristWatch << std::endl;
int id = 0;
std::cin >> id;

switch ( id ) {
case 1: {
Timekeeper* ptk = AtomicClock::getTimekeeper();
delete ptk;
break;
}
case 2: {
Timekeeper* ptk = WaterClock::getTimekeeper();
delete ptk;
break;
}
case 3: {
Timekeeper* ptk = WristWatch::getTimekeeper();
delete ptk;
break;
}
/* FALLTHROUGH */
}
return 0;
}


返信引用
たまに書き込んでみた人
 たまに書き込んでみた人
(@たまに書き込んでみた人)
ゲスト
結合: 17年前
投稿: 4
 

ちょっと心配になってみてみたのですが、
鍋奉行さん、なぜ、デストラクタを仮想とするかの意味はおわかりになっているでしょうか?
この問題は別に考えてください、単純にまずいのです。

#理解してたらごめんなさいね・・・

ファクトリー関数というものを考えるとεπιστημηさん、tetrapodさんのおっしゃることも
ありです。
#というか鍋奉行さんが書いたコードそのものでは?

私の見解を採用するならば、最低コンストラクタをprivaqteにして。delete this するよ
うな格好が正当だと思います。
要するに、生成はファクトリー関数に任せ、破棄もオブジェクトに任せるのが正当だから
です。
鍋奉行さんの書いているコードではdelete し忘れるとメモリリークしますよね?
そういうのは、いくないとおもうからなのです。

#酔っぱらってるので間違えてたら誰か補足お願いいたします(^^;

ではでは


返信引用
鍋奉行
 鍋奉行
(@鍋奉行)
ゲスト
結合: 17年前
投稿: 15
Topic starter  

たまに書き込んでみた人さん、皆さんお世話になります。
>>鍋奉行さん、なぜ、デストラクタを仮想とするかの意味はおわかりになっているでしょ
うか?
「仮想デクストラクタを持たない基底クラス」のポインタdeleteを適用すると、その結果
が未定義になるからでは?

・オブジェクトの派生クラス部分は、動的に破壊されない
・ゆえに例えば、getTimeKeeperが、AtomicClockを指し示すTimeKeeperポインタを戻す
・そのポインタにdeleteを適用
・AtomicClock部分(基底クラスであるTimeKeeperの部分にはなく、AtomicClockのデータ
メンバ)は破棄されない
・ゆえにAtomicClockのデスクトラクタも実行されない

>>ファクトリー関数というものを考えるとεπιστημηさん、tetrapodさんのおっしゃるこ
ともありです。
私自身がファクトリー関数って物が分かってないので、すみません。
>>#というか鍋奉行さんが書いたコードそのものでは?
何番目に書いたコードですか?
>>私の見解を採用するならば、最低コンストラクタをprivaqteにして。
うっかりしてました。
class AtomicClock : public Timekeeper {
  AtomicClock();
  ~AtomicClock();
public:
static Timekeeper* getTimekeeper()
{
return new AtomicClock();
}
};
>>delete this する
ここが今一なんですが
派生先のデクストラクタでdelete thisしなさいってことでしょうか?
例えば
AtomicClock::~AtomicClock() {
std::cout << ~AtomicClock running..... << std::endl;
delete this; // ←こういうこと?
}

>>酔っぱらってるので間違えてたら誰か補足お願いいたします(^^;
いいですね、熱燗ですか?最近少し寒くなってきて我が家でも鍋が増え、熱燗の美味しい
季節になりました、私はこのことでこの2、3日飲んでませんが明日あたりは、かに鍋と熱
燗で一杯いきたいところですww


返信引用
επιστημη
 επιστημη
(@επιστημη)
ゲスト
結合: 22年前
投稿: 1301
 

デストラクタ内でdelete thisした途端にデストラクタが呼び出され、
無限再帰に落ちりゃせんかしら?


返信引用
たまに書き込んでみた人
 たまに書き込んでみた人
(@たまに書き込んでみた人)
ゲスト
結合: 17年前
投稿: 4
 

仮想デストラクタの意味はおおむねそんなところですね。

> 何番目に書いたコードですか?
main の中で switch してるのを関数として外に出しただけでは?
main の中で書くよりεπιστημηさんの例通り

TimeKeeper* getTimeKeeper(int id) {
switch ( id ) {
case 0: return new AtomicClock();
case 1: return new WaterClock();
case 2: return new WristWatch();
}
return 0;
}

とした方が再利用性もいいし、ファクトリ関数っぽいのではないのでしょうか。

あと、delete this の件は忘れてください。おかしなこと言ってました。
メンバ関数で生成したのなら、メンバ関数で破棄した方が
私の好みに合っていると言うだけで、コーディングスタイルの問題です。

やるなら・・・

class AtomicClock : public Timekeeper
{
AtomicClock(); // 隠蔽して getTimekeeper,suicide の使用を強要する
~AtomicClock();
public:
static Timekeeper* getTimekeeper() { return new AtomicClock(); }
void suicide() { delete this; } // こんな感じ
};

で、こう使う

Timekeeper* ptk = AtomicClock::getTimekeeper();
ptk->suicide();

#あんましかわらんね・・・
ちなみに AtomicClock() をプライベートにしたのは、

AtomicClock tk;
tk.suicide();

と、できないようにするためです。
というわけで、結構めんどくさいです。
私自身 delete this は好きじゃないんで滅多に使いません。
ごめんなさい。(^^;

あと、デストラクタの中で delete this しちゃだめですよ。
一度、やってみるとおわかりになると思います。

ではでは・・・


返信引用
鍋奉行
 鍋奉行
(@鍋奉行)
ゲスト
結合: 17年前
投稿: 15
Topic starter  

たまに書き込んでみた人さんお世話になります
>>main の中で switch してるのを関数として外に出しただけでは?
そうですね、main(){}の外に出したら、出来ましたが、二点お質問があるのですが

[質問1.]
mainの外に出す事で、派生クラスのコンストラクタを隠蔽(private)すると、アクセス
できないのですが、この場合publicセクションに配置してもかまわないでしょうか?

[質問2.]
new AtomicClock()
で動的に生成された、コンストラクタのdeleteは何処でどのように行うのがよいのでしょ
うか?

以上宜しくお願いいたします。


返信引用
たまに書き込んでみた人
 たまに書き込んでみた人
(@たまに書き込んでみた人)
ゲスト
結合: 17年前
投稿: 4
 

#なんか、長くなってるな・・・

コンストラクタとデストラクタを private にしたのは、
メンバ関数での new と delete をユーザーに強制するために行いました。
私の提示したコードを使用した場合、

AtomicClock tk;

とすると、private なコンストラクタが呼ばれるわけなので、
コンパイラから怒られてしまうわけです。同様に、

delete ptk;

とすると、private なデストラクタが呼ばれるので、
やはりコンパイラから怒られてしまい、ユーザーは、ptk->suicide();
とするしかなく、用意されたメンバ関数を使用して「生成->破棄」
という流れを強要できるため、私的に好みなんです。というわけで・・・

[質問1.]
private なコンストラクタにしているので、自前で new できません。

> この場合publicセクションに配置してもかまわないでしょうか?
これについては、鍋奉行さんの好みで決めていいと思います。
不自然と感じるのなら、privateにしなければいいと思います
(今まで書いてきた感じでOK)。

[質問2.]
private なデストラクタを使用してますので。同様に delete ptk
のようなことはできません。使い終わったら、

ptk->suicide();

で、自殺させてください。
これをふまえ、private なコンストラクタ、デストラクタを使用する場合は、
以下のようなコードになると思います。

class Timekeeper
{
protected: // protected にしておかないと派生先で呼べないから気をつけて
Timekeeper() {}
virtual ~Timekeeper() {}
public:
virtual void suicide() = 0; // 派生先に suicide の実装を義務化する。
};

class AtomicClock : public Timekeeper
{
AtomicClock() {}
~AtomicClock() {}
public:
static Timekeeper* getTimekeeper() { return new AtomicClock(); }
void suicide() { delete this; }
};

class WaterClock : public Timekeeper
{
WaterClock() {}
~WaterClock() {}
public:
static Timekeeper* getTimekeeper() { return new WaterClock(); }
void suicide() { delete this; }
};

// 他のクラスは同様に・・・
// getTimekeeper を関数にしたかったら以下のように・・・

Timekeeper* getTimekeeper(int id)
{
switch(id) {
case 0 : return AtomicClock::getTimekeeper();
case 1 : return WaterClock::getTimekeeper();
// 作った分だけここに追加していく・・・
}
return NULL;
}

int main()
{
Timekeeper* ptk0 = getTimekeeper(0);
ptk0->suicide();

Timekeeper* ptk1 = getTimekeeper(1);
ptk1->suicide();

return 0;
}

これが冗長と感じるなら、そのまま使っちゃえばいいです。

int main()
{
Timekeeper* ptk0 = AtomicClock::getTimekeeper();
ptk0->suicide();

Timekeeper* ptk1 = WaterClock::getTimekeeper();
ptk1->suicide();

return 0;
}

こんな所ですかね・・・?コンパイルしてないので、エラーあったらごめんなさい。
また、正直これが正当派なのかは判断しかねます。
最終的には、鍋奉行さんの好みにあう方にする方がいいと思います。

ではでは・・・


返信引用
tetrapod
 tetrapod
(@tetrapod)
ゲスト
結合: 21年前
投稿: 830
 

仮想コンストラクタの話という以前の段階の話をするなら

この手の継承派生関係を理解するためには
・クラスを使う側
・クラスを提供する側
の2者がいること、および、今はそのどちらの立場で話をしているか、を
常に意識しておかないと通じなくなってしまう。

基底クラスを使う側の立場で考えるとき
・具体的にクラスの詳細実装まで知る必要は無い
・この関数をこう呼び出せばこう動く=インターフェイスのお約束
 だけ知っていれば必要十分
=派生クラスの詳細については意識するな、基底クラス側だけ知っておけ!
GoF 本の話はこっち。だから派生側の詳細については書いてないわけだ。

派生クラスを提供する側の立場で考えるとき
・仮想関数がこのように使われる=契約 (LSP)
 だから、こんな風に実装しなきゃならん
鍋奉行さんが今意識しているのはこっち。だから話が厄介になる。

仮想コンストラクタでぐぐった最初のページ
http://ncc-kk.co.jp/obj01s.html
ここにも「ダサい実装」が書いてあるよな。


返信引用
固定ページ 1 / 2

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました