が、一つ、誤解を生みかねない表現があったので勝手ながら補足しておきたいと思います。
元記事と併せてお読み頂ければ幸いです。
コリン・ムック「今から始めるActionScript 3.0」に行ってきました
例えばプリミティブ型(NumberやString)では
var a = "dog";
var b = a;
としたとき、「dog」というオブジェクトがコピーされています。
私も以前、初心者向けにJavaScriptの基礎セミナーを開催しようとして(結局客が集まらずボツになりましたが)、その際に最初に教え方で悩んだのがこの「変数の概念の説明」でした。
「変数は値を入れておく箱ですよ〜」って説明だけでは、オブジェクト変数のコピーの説明が付けられないのですよね。プリミティブ型とオブジェクト型の違いというか。
なので、私も上記ページで説明しているような図を使って「値コピー」と「参照コピー」の違いを説明しようとしました。プログラミングを学ぶ上で、メモリという概念の理解は必須です。
しかし、この説明では一つ問題があります。
なぜなら、JavaScriptやActionScript3のString型は、プリミティブ型ではあるものの、変数のコピーは値コピーではなく参照コピーになるからです。それどころか、ActionScript3では全てのプリミティブ型のコピーは参照コピーになるようです(追記:JavaScriptでは、string型以外のプリミティブ型(NumberやBooleanなど)については固定長のようなので、恐らく値コピーかと思うのですが、調べきれていません。m(__)m)。
Adobe提供の「ActionScript 3.0のプログラミング」という文書には、
ActionScript 3.0 のすべての値は、プリミティブ値または複合値にかかわらず、オブジェクトです。
(中略)
技術的に説明すると、ActionScript ではプリミティブ値はイミュータブルオブジェクトとして内部的に格納されます。イミュータブルオブジェクトとして格納されるということは、参照渡しが値渡しと同じように効果的であることを示しています。参照は通常、値自体よりかなり小さいので、これによりメモリ使用量が削減し、実行速度が向上します。
とあります。
イミュータブルオブジェクトとは、内容を変更できないオブジェクトのことです。
これはどういう事かと言うと、
var a = "dog";
a.setCharAt( 0, 'l' ); // こんなメソッドは無い
trace(a); // 'log'と出力?
のような事は出来ない、ということです。ここで架空のsetCharAtメソッドは、指定した位置の文字を上書きする、というものです。このような「内容を変更する」メソッドがString型には存在しない為、Stringオブジェクトは一度生成した後、内容が変わる事がありません。
ここで、次のようなコードをどう思うでしょうか。
var a = "dog";
var b = a;
a = "cat";
trace(a); // "cat"
trace(b); // どうなる?
これを見て、「文字列変数のコピー(代入)が参照渡しということならば、変数bをtraceすると、"cat"と出力されるのではないか」と思った人はいないでしょうか。実際はそうはなりません。
3行目の「a="cat"」は、aが指すメモリ上の"dog"を変更したのではなく、"cat"というオブジェクトを新しく作り、aが指す参照先(矢印)を"cat"に切り替えただけです。元の"dog"はそのままbが参照しています。
もちろん、
var a = "dog";
var b = a;
a.setCharAt( 0, 'l' ); // こんなメソッドは無い
のような事が出来てしまうのなら、aのtraceもbのtraceも"log"となるでしょう。
しかし文字列(を含むプリミティブ型)はイミュータブル(不変)なので、このような事は起こりません。
つまり、プリミティブ型がイミュータブルとして実装されている限り、変数のコピーが値コピーか参照コピーかを意識する必要はないのです。
しかし、それを意識しておくと良いことがあります。もし、文字列変数のコピーが「値のコピーだ」と思っている人は、次のようなコードを見て顔をしかめるかもしれません。
var a = 10万文字分の文字列;
var b = a;
彼の頭の中では、10万文字の文字列がメモリ上でコピーされているはずです。これはなんというメモリの無駄遣い! 彼は考えて、次のように実装しなおすかもしれません。
var a_wrapper = { a : 10万文字分の文字列 };
var b = a_wrapper;
こうしておけば、10万文字列はa_wrapperというオブジェクト変数にラップされたまま変数bに渡される為、文字列の実体がコピーされることはない、というわけです。
皆さんは既に「文字列のコピーは参照コピー」ということを知っている為、これが無駄な努力だということが分かるかと思います。
JavaScriptの話になりますが、こちらに良いまとめ記事があったのでご紹介しておきます。
プログラマのためのJavaScript (3):参照について考えてみる
元の記事に戻ると、元記事では、文字列変数のコピーの際にメモリ上でも実体がコピーされるような説明をしてしまっているのですよね…。これはコリン・ムック氏の資料自体がそうなっているようなのでしょうがないと思いますが、実装から見ると誤解を与えかねません。
しかし、所謂「初心者」にいきなり「イミュータブルオブジェクト」などの話を持ち出すと、余計こんがらがるような気がしませんか? 私が最初に悩んだのはそこでした。
多少嘘が入ってでも、元記事のような説明をすべきなのか、それとも、他の方法を考えた方がいいのか…。
結局、答えが出る前にセミナー自体がボツになってしまったので、未だに答えは見つかっていません。(^^;
元記事は、オブジェクトとメモリ、変数、参照、GCなどの考え方が非常に良く分かる良い記事だと思います。
【追記】
プリミティブ型にも関わらずイミュータブルオブジェクトとして実装されている、という点について、一つ重要な点を書いておくのを忘れました。単にそれらがイミュータブルオブジェクトであるというだけなら、「プリミティブ型」などと言わなくても「イミュータブルオブジェクト」と呼べば良いはずです。
わざわざ「プリミティブ型」と説明されているのには理由があります。
それは、「比較演算」の際に意味を持ちます。
var a = "dog";
var b = "dog";
if ( a == b )
{
trace( "bingo!" );
}
string型が単に「イミュータブル(不変)なオブジェクト」であるだけなら、a == bはfalseを返さなくてはなりません。aの"dog"とbの"dog"は別のタイミングで作られた別のオブジェクトであり、それらの参照が一致することはないはずです。
しかしもちろんこの例では、"bingo!"が出力されます。a == bは trueを返します。もしこれがfalseを返すようだと、ソースコードが読みづらくてかないません。
これは、string型の比較の際には参照の比較ではなく、参照先の値の比較をするようになっているからです。string型がプリミティブ型として分類されている理由は、この為です。
【追記 2008.01.22】
プログラミング中級者以上ならばこの記事は当たり前の事として受け入れられると思っていたのですが、どうもそうでもなさそうなのでもう少し補足します。確かに、意識したことない人は多いかもしれない…。
ミュータブルな文字列という概念を知ったのは、私の場合、Javaが最初でした。
JavaにはStringクラス以外にStringBufferというクラスがあり、それらの違いはStringクラスがイミュータブルで、StringBufferがミュータブルだという点でした。「なぜそんな役割分担が?」と疑問に思って調べていくと、文字列変数のコピーを参照コピーで済ます為のものだと分かったのでした。それまで、VB6などを使っていたのですが、文字列変数コピーに対してなんの疑問も持っていなかったので新鮮でした(VB6の文字列も内部的にはポインタなのですが、イミュータブルだったかどうか覚えていません。Mid関数とかあるし。なんとなく、振る舞い的にはコピーオンライトなような気がします)。
ちなみにJavaの場合、String型の==演算子での比較は参照の比較となり、意図した結果になりません。文字列の一致比較を行いたい時は、==ではなくequalsメソッドという専用のメソッドを使います。これは当時でも「それは面倒だな」と思った覚えがあります。
この点、C#では文字列の==演算を内部的にEqualsメソッド呼び出しに置き換えてくれる為、見やすく記述可能です。(.NETの文字列もイミュータブルです)
rubyでは文字列はミュータブルとして実装されているそうです。イミュータブルなのはシンボル、ということのようです。==が値の比較でequals?が参照の比較ってのはJavaと逆なんですね。
Cプログラマにとっては文字列はchar*なので、最初から参照(正確にはポインタ。C++では参照とポインタが別の用語なのでややこしい…)ですね。
いろいろな実装があって面白いですね。とはいえ、文字列変数コピー(代入)を値コピーで実装している言語処理系は、パフォーマンスの観点から恐らくほとんどないと思うので、それがイミュータブルかどうかはともかく、このタイトルどおり「文字列変数コピーは参照コピーである」と覚えておけば充分ではないかと思います。









ご指摘ありがとうございます、記事の方からこちらの記事にリンクをさせていただきましたが、よろしかったでしょうか?
事後報告で申し訳ありません。
ミュータブル、イミュータブルについて、あまり深く考えた事がなかったので大変参考になりました。 — func09
元記事のおかげで理解が進んだ人も多いと思います。こういう記事がもっと増えると良いですね。 — chikura @ 08:42PM 2008-01-24