2005年05月06日
川俣晶の縁側ソフトウェア技術雑記total 31608 count

C++/CLIはC#を凌駕するかも知れない…… usingステートメント不要のDisposeメソッド呼び出しの衝撃

Written By: 川俣 晶連絡先

 どうもC++に関して、大きな思い違いをしていたかも知れない、と気付かされました。

 Visual C++ 2005あるいは、おそらくはC++/CLIは、私が思っていた以上に凄いかもしれません。

 その凄さは、ある種の生産性においてC#を凌駕するかもしれません。

お断り §

 以下の文章は、夜中に突発的に書いたテストコードの動作結果をメモっておくために書いたもので、用語や解釈などが正しくない可能性があります。

 ただし、Visual C++ 2005 Express Beta2で実行した結果について記していますので、ソースと実行結果の対応関係は間違いないと思います。

C#のusingステートメントとDisposeパターン §

 C#がJavaやVisual Basic.NETと比較して圧倒的に優れていると考えるポイントはいくつかありますが、その1つがusingステートメントとDisposeパターンです。リソースの寿命を明示的に強制するのに役立つ、強力な機能です。これがあるという理由だけでも、C#を勧める強い動機になっていました。(詳細略)

 そして、Visual Basicでも2005バージョンでこれが追加されるので、C#の優位性は過去のものになるだろうと考えていました。

C++/CLIにおけるDisposeパターン §

 C++/CLIでは、デストラクタがDisposeメソッドに相当します。

 そして、参照型(refキーワードの付いた)クラスを、参照スタイルではないローカル変数として記述した場合、スコープを抜けた時点でDisposeメソッドが呼び出されます。

 つまり、C#や2005バージョンのVisual Basicでは、usingステートメントという書式を明示的にプログラマが記述しなければ実現されなかった機能が、明示的なステートメントを書くことなく実現されるということです。また、クラスを宣言する際に、明示的にIDisposableインターフェースを実装するコードを記述する手間も消滅します。

 デストラクタとは別に、C++/CLIにはファイナライザの構文が追加されます。これは、デストラクタが~で書き始めるのに対して、!で書き始めるという相違があります。

サンプルソース §

#include "stdafx.h"

using namespace System;

ref class Test 

{

public:

    // コンストラクタ

    Test()

    {

        Console::WriteLine(L"Constructed");

    }

    // デストラクタ (Disposeメソッド)

    ~Test()

    {

        Console::WriteLine(L"Destructed");

    }

    // ファイナライザ

    !Test()

    {

        Console::WriteLine(L"Finalized");

    }

};

void useDispose()

{

    Test test;

    Console::WriteLine(L"in useDispose");

}

void useGCNew()

{

    Test ^ test = gcnew Test();

    Console::WriteLine(L"in useGCNew");

}

int main(array<System::String ^> ^args)

{

    if( args->Length > 0 && args[0] == L"dispose" )

    {

        useDispose();

    }

    else

    {

        useGCNew();

    }

    Console::WriteLine(L"in main");

    return 0;

}

実行結果 §

C:\……\debug>cppnet001

Constructed

in useGCNew

in main

Finalized

C:\……\debug>cppnet001 dispose

Constructed

in useDispose

Destructed

in main

C:\……\debug>

 gcnewでインスタンスを生成した場合は、デストラクタつまりDisposeメソッドは呼ばれません。そして、プログラム終了時にファイナライザが呼ばれてインスタンスは消滅します。

 一方、Test test;と参照ではないローカル変数スタイルでインスタンスを生成した場合は、スコープを抜ける時点でデストラクタつまりDisposeメソッドが呼ばれています。つまり、スコープがC#のusingステートメント的な機能を発揮していますが、特定のステートメントは存在しません。そして、ファイナライザは呼ばれていません。

 おそらく、このインスタンスが確保したメモリは、ガベージコレクションが行われた時点で回収されているのではないかと思います。(ここは不確か)2008/08/20追記。このインスタンスを格納するために確保されたメモリは、ガベージコレクションが行われた時点で回収されます。このケースにおいて、Testは参照型であり、スタックフレーム上に確保されるのは参照だけです。参照されるオブジェクトの実体は、ガベージコレクションで回収される参照型オブジェクトとしてスタックフレーム外に確保されます。ちなみに、Test ^ test = gcnew Test();として確保しても、Test test;として確保しても、実装としてはどちらもnewobj instance void Test::.ctor()として確保されます。つまり、確保するスタイルは同じです。(Visual Studio 2008によるデバッグビルドを.NET Reflectorで逆アセンブルして確認)

まとめ: リソースの寿命の明示的な管理の重要性 §

 リソースの寿命を明示的に管理することは、プログラムを安定動作させるために重要な意味を持ちます。

 いかなる例外的な状況でもリソースの破棄処理ができるように、様々な方法が工夫されてきました。

 たとえばJavaでは例外のfinally構文によって、リソースの破棄処理を確実に実行できるように工夫されています。しかし、頻繁に使う割に記述する文字数が多く、イマイチ使い勝手が良くありません。また、変数のスコープと例外構文のスコープが連動していないという問題もあります。

 C#のusingステートメントは、より短い文字数でかつ変数のスコープとリソースの寿命が一致するというナイスな機能でした。

 しかし、もしもリソースの管理とは「当然行うべきもの」「やって当たり前」と考えるならば、いちいち特定ステートメントを記述することを要求するのは、不適切ということになります。つまり、「やって当たり前」であれば、それは常に実行されるようにプログラム言語の構文を工夫する価値があります。そして、C++/CLIは「スコープ=Disposeパターンの発動」という境地に達したのです。このような進化は、私にとっては、極めて好ましいものに思えます。そして、おそらくは生産性の向上に寄与するでしょう。

 つまり、総合力としての生産性はともかくとして、この1点に限れば、C++/CLIの生産性はC#を凌駕する可能性があり得ると考えられるのです。

余談: 他にもいろいろ §

 資料を読んでいて、C++関係で驚かされることは他にもいろいろあります。おそらく、プログラマの大半の人達が想像するよりも何倍も凄いです。解説する人が少ないために、知識が広まっていないだけだと思いますが。(なに? おまえが書け? どこかの出版社が原稿依頼をしてくれれば書けますが……)

 また、OOPの本質とその使い方についてのノウハウの蓄積が増えた今なら、かつてC++を持てあました時代よりも、ずっと上手くC++を使いこなせるのではないかという考えもあります。

 それゆえに、今、C++とは私が最も熱意を持って注目しているプログラム言語であると断言できます。

Facebook

このコンテンツを書いた川俣 晶へメッセージを送る

[メッセージ送信フォームを利用する]

メッセージ送信フォームを利用することで、川俣 晶に対してメッセージを送ることができます。

この機能は、100%確実に川俣 晶へメッセージを伝達するものではなく、また、確実に川俣 晶よりの返事を得られるものではないことにご注意ください。

このコンテンツへトラックバックするためのURL

http://mag.autumn.org/tb.aspx/20050506023118
サイトの表紙【技術雑記】の表紙【技術雑記】のコンテンツ全リスト 【技術雑記】の入手全リスト 【技術雑記】のRSS1.0形式の情報このサイトの全キーワードリスト 印刷用ページ

管理者: 川俣 晶連絡先

Powered by MagSite2 Version 0.36 (Alpha-Test) Copyright (c) 2004-2021 Pie Dey.Co.,Ltd.