仮想デストラクタを持たないクラスからの派生

[戻る]
シャノン 2004/04/23(金) 14:47:39
仮想デストラクタを持たないクラスから派生クラスを作成した場合、
派生クラスを new で作成し、そのポインタを基底クラスポインタ型で受け取って
delete すると、派生クラスのデストラクタが呼ばれませんよね。

class CBase // 仮想デストラクタを持たないクラス
{
public:
  Base();
  ~CBase(); // 仮想ではない
};

class CDerived : public CBase
{
};

CBase *pBase = new CDerived;
delete pBase;

こんなケース。

こんな場合でも、派生クラスのデストラクタですべきこと(リソースの開放とか)が必
要なければ、継承していいんでしょうか?
それとも、派生クラスのデストラクタですべきことが出来ない以外になにかデメリット
があるんでしょうか?

tetrapod 2004/04/23(金) 15:39:16
基底クラス (CBase) の実装がいじれないもの、として考えます。

>こんな場合でも、... 継承していいんでしょうか?
派生クラス側に(デストラクタが必要な)メンバが持てないのを承知の上で
開発商品のライフタイムに渡ってクラスの実装が変わらないなら、
自己リスクにおいてどうぞ、と燃料投下してみましょう。

こういう場合は派生ではなくメンバにしちゃうのがよさげ。
または public 継承ではなく private 継承にすべきでしょう。
少なくとも自分なら public 継承以外の設計を選びます。

STL コンテナを継承してどーこう、という設計を何度か考えたことが
ありますが、最終的にはやはりコンテナをメンバに持つようなクラス
設計に落ち着いています。

SunPac 2004/04/23(金) 15:57:38
CDeliverをさらに派生したくなった時はデストラクタが呼ばれないとばずいのでは?
デメリットはそれくらいかも。
強引にキャストしてしまえばいいんですけどね。

delete static_cast<CDeliver *>(pBase);

ってなぐあいに。
私ならpublicな継承するのならデストラクタをvirtualにしますけど。

PATIO 2004/04/23(金) 16:00:03
>派生クラス側に(デストラクタが必要な)メンバが持てないのを承知の上で
>開発商品のライフタイムに渡ってクラスの実装が変わらないなら、
>自己リスクにおいてどうぞ、と燃料投下してみましょう。

まあ使用範囲が限られるような一時的なツールであればともかく、
商品を開発する上ではやりたくない話ですね。
特定ユーザー向けの開発をしていれば、当初の仕様にさらに追加仕様が発生する
なんて事はよくある話ですし、そういった状況下で上記のような制約を課せられるのは
かなり面倒な状況だと思います。
tetrapodさんが言われているようにどうしてもラップしたいのであれば、
クラスのメンバーにするという手もありますし、そういった状況ではそれを選ぶ方が
良いと思います。
逆に言うと仮想のデストラクタが無いという事は派生するなという設計者の意図と
取れなくもないかなと思いますし。

シャノン 2004/04/23(金) 16:02:43
レスありがとうございます。

>基底クラス (CBase) の実装がいじれないもの、として考えます。
その通りです。説明不足でした。

>派生クラス側に(デストラクタが必要な)メンバが持てないのを承知の上で
承知の上です。ほぼ完成しているクラスにちょっと機能を加えるだけなので大丈夫で
す。…たぶん。

>開発商品のライフタイムに渡ってクラスの実装が変わらないなら
仕事ではなく個人の趣味プログラミングなので、いくらでも仕様を変えられるので問題
ないです。…というか仕様書なんか書いちゃいません。

>こういう場合は派生ではなくメンバにしちゃうのがよさげ。
>または public 継承ではなく private 継承にすべきでしょう。
>少なくとも自分なら public 継承以外の設計を選びます。
基底クラスにある機能をそのまま使いたいため、public にしています。
private 継承や包含にすると、自身と基底クラス間を取り持つコードを書かねばならん
のが面倒で…。

シャノン 2004/04/23(金) 16:11:31
うおぉ、レスを書いてる間にまたレスがっ!
皆さんありがとうございますー

>CDeliverをさらに派生したくなった時はデストラクタが呼ばれないとばずいのでは?
上にも書きましたが個人の趣味プログラミングなのでそこまで抽象化できてないです。
その必要が出てきたら CDeriver を直接いじっちゃうと思います。

>私ならpublicな継承するのならデストラクタをvirtualにしますけど。
CBase がいじれないんですよねー。シクシク

>まあ使用範囲が限られるような一時的なツールであればともかく、
>商品を開発する上ではやりたくない話ですね。
思いっきり限られてますので大丈夫です。

>逆に言うと仮想のデストラクタが無いという事は派生するなという設計者の意図と
>取れなくもないかなと思いますし。
なるほど、そういう読み方も出来ますか…。
でもその場合なら、もっと強固な方法で禁止できると思います。コンストラクタかデス
トラクタを private にするとか。…あ、スタック上に確保できなくなるか。
この場合はただ、継承されることを想定してなかっただけのような気がしますが。

こじま 2004/04/23(金) 20:57:17
こういうのって、テンプレートを使ってこちょこちょやると、
あら不思議ってな方法がありそうな感じがするけど。。思いつかないです。

PATIO 2004/04/26(月) 14:16:42
>>逆に言うと仮想のデストラクタが無いという事は派生するなという設計者の意図と
>>取れなくもないかなと思いますし。
>なるほど、そういう読み方も出来ますか…。
>でもその場合なら、もっと強固な方法で禁止できると思います。コンストラクタかデス
>トラクタを private にするとか。…あ、スタック上に確保できなくなるか。
>この場合はただ、継承されることを想定してなかっただけのような気がしますが。

Javaだとfinalって修飾子があるんですけれどねぇ。
C++にはこれに相当するものがなかったと思うので、
判断基準としては、デストラクタが仮想関数になっていないくらいしかないかなと。

まあ、finalのような修飾子がない以上、
継承される事を想定していなかったのか、してほしくなかったのかは、
コードを書いた本人のみぞ知るって事になるのでしょうねぇ。

ぽち 2004/04/26(月) 17:42:28
> でもその場合なら、もっと強固な方法で禁止できると思います。コンストラクタかデ> トラクタを private にするとか。…あ、スタック上に確保できなくなるか。

delete演算子をprivateにしてしまえば、仮想デストラクタがないことによる不具合は
回避できるような気がします。


> Javaだとfinalって修飾子があるんですけれどねぇ。
> C++にはこれに相当するものがなかったと思うので、

強引な方法としては、unionを使うというのもあります。
unionから派生することはできないので。

tetrapod 2004/04/27(火) 08:48:34
ちょーど同一の話題が出てたので紹介しときます。
CPPLL ML Archive より
http://www.tietew.jp/cppll/archive/10658

PATIO 2004/04/27(火) 10:17:12
いい記事を紹介していただきました。
この方法なら確かに派生が禁止できますね。
しかもこれだとClassBをローカルに確保できますね。
ClassCがClassAのコンストラクタを呼べない事がミソであると。
今のところ、派生禁止をしないといけない事態になった事がないので
実際に使う機会はあまり無さそうですけれど。

デストラクタが仮想関数で無い事を継承不可のサインと見るというのは
私もEffective C++で読んでました。
ただ、拘束力が薄いというのは確かですね。
上記の例のようにビルドでエラーになりませんから。

シャノン 2004/04/28(水) 12:45:45
[[解決]]
うわーうわー解決忘れてましたごめんなさーい
古いスレを上げて申し訳ないですが、解決ポチっとな。

こじま 2004/05/12(水) 01:58:26
こんな、古いスレに書いてもしょうがないけど、
boost::shard_ptr
って、まさに、仮想デストラクタじゃないクラスの継承クラスでもOKなんですね。


class CBase {
public:
  ~CBase(); // 仮想ではない
};
class CDerived : public CBase {};

int main()
{
  boost::shared_ptr<CBase> p( new Derived ); // OK
  return 0;
}

実装見てなるほどと思いました。頭いい人いるね。

古いスレッドには追加発言できません。