発端は、2ch で見かけた、この発言 JavaScript.
まずは、こちらをご覧ください。 (注: 例示の JavaScript は SpiderMonkey や Rhino のインタプリターで実行するか、print を alert に読みかえてブラウザーで実行してください。)
// __GooglePrettify__
function func1(a) {
a = {hoge: 2};
}
var b = {hoge: 1};
func1(b);
print(b.hoge); // => 1
実行すると、1 と表示されましたよね。「参照渡し(call by reference)なら、ここで 2 と表示されなければおかしい。これは、値渡し(call by value)じゃないの?」というのがリンク先で投げかけられていた疑問です。
ここで、次の関数を考えてみます。
// __GooglePrettify__
function func2(a) {
a.hoge = 2;
}
var b = {hoge: 1};
func2(b);
print(b.hoge); // => 2
今度は 2 と表示されたと思います。この挙動を指して、参照渡しだと説明された場合は納得できると思います。
ここで、突然ですが、視点を変えて C++ で同様の関数をつくってみましょう。
// __GooglePrettify__
#include <iostream>
using namespace std;
class Hoge
{
public:
int hoge;
Hoge(int a) : hoge(a) {}
};
void func_v1(Hoge a)
{
a = Hoge(2);
}
void func_v2(Hoge a)
{
a.hoge = 2;
}
void func_r1(Hoge &a)
{
a = Hoge(2);
}
void func_r2(Hoge &a)
{
a.hoge = 2;
}
int main(void)
{
{
Hoge b(1);
func_v1(b);
cout << b.hoge << endl; // => 1
}
{
Hoge b(1);
func_v2(b);
cout << b.hoge << endl; // => 1
// まぁ、値渡しですから。
}
{
Hoge b(1);
func_r1(b);
cout << b.hoge << endl; // => 2
// おっ!?
}
{
Hoge b(1);
func_r2(b);
cout << b.hoge << endl; // => 2
// うんうん。これこそ、参照渡しです。
}
return 0;
}
同じような事をするのに、2 倍のパターンが出てきました。さてさて、JavaScript はどちらのパターンでしょう。 結論から言うと後者の _r 付きのパターンにあたります。これが参照渡し(call by refference)です。 _v 付きは、もちろん値渡し(call by value)です。
ここで、よ〜く見ると、func_r1 と func1 の挙動が異なりますね。 ここで更に C++ について学んで行きましょう。
// __GooglePrettify__
#include <iostream>
using namespace std;
class Hoge
{
public:
int hoge;
Hoge(int a) : hoge(a) {}
};
void func_p1(Hoge *a)
{
a = new Hoge(2);
}
void func_p1_(Hoge *a)
{
*a = Hoge(2);
}
void func_p2(Hoge *a)
{
a->hoge = 2;
}
int main(void)
{
{
Hoge b(1);
func_p1(&b);
cout << b.hoge << endl; // => 1
// あらあら (このままだとメモリーリークします)
}
{
Hoge b(1);
func_p1_(&b);
cout << b.hoge << endl; // => 2
// おぁ!?
}
{
Hoge b(1);
func_p2(&b);
cout << b.hoge << endl; // => 2
// うんうん
}
return 0;
}
またしても分裂してしまいました。ここで、func1 と同じものは func_p1 です。func_r1 と同じものは func_p1_ です。(本当は、参照は書き変え不可な所が違いますが。) func_p1 と func_p1_ の違いのポイントは、代入時の左辺値が a か *a か、という点です。 これによって、処理の対象が "ポインタ" か "ポインタが指し示している先のオブジェクト" か、という違いが生じます。 C++ では参照を用いることで、"*" を使わずに "参照先のオブジェクト" に対して処理を実行することができます。
ところで、先程の C++ の関数はこうも書けます。
// __GooglePrettify__
#include <iostream>
using namespace std;
class Hoge
{
public:
int hoge;
Hoge(int a) : hoge(a) {}
};
void func_r1_(Hoge &a)
{
a.operator=(Hoge(2));
}
int main(void)
{
{
Hoge b(1);
func_r1_(b);
cout << b.hoge << endl; // => 2
// おおっ!?
}
return 0;
}
この書き方のほうが、参照先のオブジェクトの代入演算子を実行している…、参照先のオブジェクトのメンバ関数 operator= を実行しているということがわかりやすいと思います。 ポインタによる参照渡しの例 func_p1 に戻ると、こっちは参照先自体(ポインタ)を入れ替える処理が実行されます。そしてこれは、JavaScript での参照渡しと同様の動作です。
func_p1 と func_r1 の差はここです。
C++ での参照は、一貫して参照先のオブジェクトに○○を適用という感じです。 JavaScript でも、C++ のそれとだいたい同じなのですが、代入の時だけは参照を貼り替えるという動作になります。
なんとなくイメージはつかんでいただけたかなと思うのですが、いかがでしょう?
最後に、C++ では参照先のオブジェクトに対して代入演算子が呼ばれると言いましたが、この動作と同じようなことを JavaScript でやってみましょう。
// __GooglePrettify__
function assign(that){
if (this === that) return this;
for (var i in that) if (that.hasOwnProperty(i)) {
this[i] = that[i];
}
return this;
}
function func1_(a) {
a.assign = assign; // Assign method を生やして...
a.assign({hoge: 2});
}
var b = {hoge: 1};
func1_(b);
print(b.hoge); // => 2
この様に、ちゃんと値が書き変わりました。
おわり。(続編は有りません。)
参考文献
- Effective C++ 原著第3版 私はこれで C++ を学びました ;) 持ってるのは改訂第2版だけど。
- 新訂版 More Effective C++ 私はこれ(ry こっちも、新訂版は持ってない!
- Effective STL—STLを効果的に使いこなす50の鉄則 3 冊セットということで、ついでに。
- JavaScript 第5版 JavaScript といえば、この本。
- JavaScript: The Good Parts —「良いパーツ」によるベストプラクティス JavaScript での Effective C++ 的な本。(だと個人的に思う。)