#pragma twice

KAB-studio > プログラミング > #pragma twice > 383 Version 17.28 スマートポインタのまとめ

#pragma twice 383 Version 17.28 スマートポインタのまとめ

前のページへ 表紙・目次へ 次のページへ

 Version 17.28
スマートポインタのまとめ

さて、今回は前回まで説明してきたスマートポインタをまとめます
なんかすっごく複雑だったんだけど……
まず、スマートポインタの目的は、 new した変数を〈使わなくなった〉
時に delete する、というものです
使ってるときに delete しちゃダメだし、 delete し忘れたら
メモリリークしちゃうから、ってことね
そういうこと。つまり、 Version 17.21 ( No.376 ) の以下の箇所でして
いる delete を、自動的に行いたいわけです

int WINAPI WinMain
    ( HINSTANCE p_hInstance
    , HINSTANCE p_hPrevInstance
    , LPSTR p_pchCmdLine
    , int p_iCmdShow
    )
{
    // 出力用に、 CPrinter クラスのポインタを受け取ります。
    CPrinter *pcPrinter;

    // まずデバッグ用を受け取ります。
    pcPrinter = GetPrinterInstance( DEBUG_PRINTER );
    // 出力します。
    pcPrinter->Output( "あいうえお\n" );
    // 解放します。
    delete pcPrinter;   ←ここと、

    // 次にダイアログ用を受け取ります。
    pcPrinter = GetPrinterInstance( DLG_PRINTER );
    // 出力します。
    pcPrinter->Output( "あいうえお\n" );
    // 解放します。
    delete pcPrinter;  ←ここ。

    return 0;
}

スマートポインタを使用することで、これが可能になります

int WINAPI WinMain
    ( HINSTANCE p_hInstance
    , HINSTANCE p_hPrevInstance
    , LPSTR p_pchCmdLine
    , int p_iCmdShow
    )
{
    // 出力用に、 CSmartPointer クラスを受け取ります。
    // まずデバッグ用を受け取ります。
    CSmartPointer cSmartPointer = GetPrinterInstance( DEBUG_PRINTER );
    // 出力します。
    cSmartPointer.GetPointer()->Output( "あいうえお\n" );

    // 次にダイアログ用を受け取ります。
    cSmartPointer = GetPrinterInstance( DLG_PRINTER );
      ↑ここで以前持っていたポインタを delete します。

    // 出力します。
    cSmartPointer.GetPointer()->Output( "あいうえお\n" );

    return 0;
      ↑ここで持っていたポインタを delete します。
}

こうすることでメモリリークしなくなるわけね。でも……
でも?
難しすぎるよこれ! こんなの絶対作れないし、使うのもかなり無理!
うん、難しいと思うよ
へ?
これは、 C++ 言語の〈一番難しい部分〉です
一番難しいとこなの?
そう。 C++ 言語には、コンストラクタと演算子のオーバーロードの複雑
なルールがあって、これをパズルのように組み合わせることで
スマートポインタのような特殊なクラスを作ることができるんです
パズル……
そう、本当にパズル。元々のルール、 C++ 言語の複雑な言語仕様をまず
熟知しなきゃいけないし、それを使いこなせなきゃいけない。それはかなり
大変なこと。これができたら上級者って言ってもいいと思うよ
そんな難しい部分なんだ……
実際、スマートポインタを作るのはかなり大変です。もしミスがあったら
メモリリークが発生したりまだ使用中なのに delete してしまう可能性があ
るから
ミスってどんな?
たとえばコピーコンストラクタで参照カウンターを増やし忘れるとか

    // コピーコンストラクタ。
    CSmartPointer( const CSmartPointer &p_rcSmartPointer )
        : m_pcPrinter( p_rcSmartPointer.m_pcPrinter )
        , m_piRefCounter( p_rcSmartPointer.m_piRefCounter )
    {
                // ← AddRef() を呼んでない。
    }

これを呼び忘れると、以下の箇所で問題になります

                           return cSmartPointer;
                                     ↓
    CSmartPointer cSmartPointer = cSmartPointer;
                     (左)              (右)

右の cSmartPointer はすぐになくなるけど、 AddRef() メンバ関数を呼
び出していないと参照カウンターが 0 になっちゃって
げ、その場で delete されちゃう!?
というわけ
……でもさ、そんなミスってする?
するんだよねこれが。たとえば〈コピーコンストラクタを作り忘れた〉と

……へ?
このプログラム、コピーコンストラクタがなくてもビルドできるでしょ
げ! なんで……あ! そだ、コピーコンストラクタって元々作られてる
コンストラクタだった!
そう、 Version 16.11 ( No.338 ) と Version 16.12 ( No.339 ) で説明
したように、コピーコンストラクタは作らなくても元々存在してるんです
作らなかった場合、メンバ変数をコピーする……つまり、さっきの 
AddRef() メンバ関数を呼んでないコピーコンストラクタと同じ!
そういうこと。だから、スマートポインタにコピーコンストラクタを作り
忘れるとそれだけでバグになるわけです
うわぁ……
これを忘れないようにするためには、コピーコンストラクタっていうもの
が存在すること、それを自分で作れることを知らないといけないわけです
……でも、あのときも思ったけど、見えないところでそういう機能が動い
てる、っていうのを知らないといけないって大変だね
だから一番難しい部分、ってこと。こういう〈自動的に行われている部分〉
っていうのは、普通にプログラムを組んでいる時には〈気にしなくてもいい〉
ところなんです
だよね、勝手にしてくれて便利ってゆーか
だけど、スマートポインタみたいなものを作るためには、その奥まで知っ
ていないといけない、というわけなんです
うわぁ……
さすがにここまで勉強するのは相当大変だけど、 C++ を使ううえではい
つか必要になる知識だから
それまでこつこつ勉強ね……
さて、ちょっと話を戻して、さっきはコピーコンストラクタを作らないと
うまく動かない例を見てもらったけど、ちゃんと作ってても使い方次第で
正しく動作しない場合があるんです
げ! それってどんなとき?
具体的には、中で持っているはずのアドレスを〈直接〉扱ってしまうと
うまく動きません。たとえば

    // スマートポインタを受け取りました。
    CSmartPointer cSmartPointer = GetPrinterInstance( DEBUG_PRINTER );
    // このなかにあるアドレスを、取り出してポインタに代入します。
    CPrinter *pcPrinter = cSmartPointer.GetPointer();

そか、スマートポインタの中のアドレスって GetPointer() メンバ関数で
取得できるんだったね。でもこれがいけないの?
これをしてしまうと、ひとつのアドレスが cSmartPointer と pcPrinter 
の両方に入ってることになります。こうなってしまうと、スマートポインタ
では管理しきれません
どして?
スマートポインタは〈もう参照先の変数を使わなくなったとき〉、つまり
〈アドレスがどのポインタにも入ってないとき〉に delete で削除します
うん、それがスマートポインタの目的だもんね
でも、普通のポインタにアドレスが入れられていても、スマートポインタ
はそのことは分からないから……
げ! そか、スマートポインタだけ使ってたら〈全部なくなった〉ってい
うことが参照カウンターでわかるけど、普通のポインタも混ざってると……
だから、たとえばこういうことが起きてしまいます

    // ポインタだけ先に作ります。
    CPrinter *pcPrinter;

    // ブロック(実際には if とか for とか)。
    {
        // スマートポインタを受け取りました。
        CSmartPointer cSmartPointer 
            = GetPrinterInstance( DEBUG_PRINTER );
        // このなかにあるアドレスを取り出してポインタに代入します。
        pcPrinter = cSmartPointer.GetPointer();

        // このブロックから抜けると、スマートポインタは
        // 中のポインタを delete してしまいます。
    }
    pcPrinter->Output( "あいうえお\n" );
    // ↑ダメ! もう delete されたアドレスを使ってます!

えと、ブロックの中でさっきと同じことしてるんだよね。スマートポインタ
作って、その中のアドレスを外のポインタに渡して……げ! そか、ブロック
から出るとスマートポインタがなくなっちゃうから……
デストラクタが呼び出されて中にあるアドレスが delete されてしまうん
です
そうすると、 pcPrinter で持ってるアドレスも同じだから、もう使えな
くて……つまり、普通のポインタを持っていると、こういうふうに使えなく
なったアドレスを持たされるハメになる、と
そういうこと。逆に、普通のポインタが、スマートポインタの悪さをする
可能性もあるんです
どゆこと?
たとえば、勝手に delete しちゃうとか

    // スマートポインタを受け取りました。
    CSmartPointer cSmartPointer = GetPrinterInstance( DEBUG_PRINTER );
    // このなかにあるアドレスを、取り出してポインタに代入します。
    CPrinter *pcPrinter = cSmartPointer.GetPointer();

// (ここで何か処理)

    // 使い終わったから、ポインタを delete しましょう。
    delete pcPrinter;
    // でもこのあと cSmartPointer 変数でも delete するから
    // 二重に delete されてしまいます……。

ああーっ! そか、確かにポインタがあったら delete したくなっちゃう
ね……
実際のプログラムではもっと複雑になるから、こういうことはより起こり
やすくなります。で、こういうミスをしないためにも、仕組みを知ってる
必要があるわけです、が
それは実はかなり複雑、 C++ じゃ一番難しいレベル
でも、ポリモーフィズムでは new や delete は必要不可欠
 Version 17.21 ( No.367 ) でそう言われました……
だから、ずーっと勉強していったその先に、こういう分野があるっていう
ことを覚えておいて
ちょっと忘れたいかも……

/*
    Preview Next Story!
*/
なんか今回はへこんだかも……
まぁこの辺は本当に難しい部分だからね……
ここが一番難しい? 難しいとこってここくらい?
あとは const メンバ関数くらいかな
げ、イヤなこと思い出させられた
というわけで次回
< Version 17.29 継承とコンストラクタ >
につづく!
ポリモーフィズムも実はもっと複雑だし、他にもテンプレートとか……
いやっもう聞きたくない!!
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。