6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++er から見た JavaScript の参照について

Last updated at Posted at 2023-04-22

はじめに

C++er と言っていますが競技プログラミングでしか C++ を使ったことがありません

主張

参照の値渡しを参照渡しと呼ぶのはやめろ!

本文

JavaScript は,

プリミティブ型の変数は値渡しをされ, オブジェクト型の変数は参照渡しをされる

と言われることがあると思います [要出典]

ここで言っているのは, 関数の引数に渡した変数に破壊的な変更を加えたときに, その変数がプリミティブ型なら呼び出し元は何も変わらないが, オブジェクト型なら呼び出し元の変数から見たオブジェクトも書き変わっている ということです

function f(a) {
  a.push(4);
}

a = [1, 2, 3];

console.log(a); // [ 1, 2, 3 ]

f(a);

console.log(a); // [ 1, 2, 3, 4 ]

そのこと自体はそうなのですが, この 参照渡し という言葉を C++ における 参照渡し と同じ意味だと思ってしまうと, 非常に困ったことになります

それは, オブジェクトに変更を加えたとき ではなく, オブジェクトを代入したとき に起こります

さて, C++ における 参照渡し とはどのようなものでしょうか
JavaScript とは違い, 型によって決まっているのではなく, & を付けることによって値渡しか参照渡しか切り替えることができます

#include <iostream>
#include <vector>

void output(const std::vector<int> &v){
  for(int e : v) std::cout << e << " ";
  std:: cout << "\n";
}

void f(std::vector<int> &v){
  v.push_back(4);
}


int main() {
  std::vector<int> a = {1, 2, 3};

  output(a); /// 1 2 3

  f(a);

  output(a); // 1 2 3 4

}

このように, 関数内で行った引数への操作が, 呼び出し側の変数にも反映されます
ここまでは同じようですが, 次のように書いた場合が問題です

#include <iostream>
#include <vector>

void output(const std::vector<int> &v){
  for(int e : v) std::cout << e << " ";
  std:: cout << "\n";
}

void f(std::vector<int> &v){
  v = std::vector<int>{1, 2, 3, 4};
}


int main() {
  std::vector<int> a = {1, 2, 3};

  output(a); /// 1 2 3

  f(a);

  output(a); // 1 2 3 4

}

このように, 引数になった変数への代入も, 元の変数に反映されています

同じことを JavaScript で行ってみましょう

function f(a) {
  a = [1, 2, 3, 4]
}

a = [1, 2, 3];

console.log(a); // [ 1, 2, 3 ]

f(a);

console.log(a); // [ 1, 2, 3 ]

このように, 代入が反映されていません

参照している先のオブジェクトを破壊的に書き換えている訳ではなく新しいオブジェクトへ参照先を変えているわけですが, その変更が元の変数には反映されないわけです

この現象は, C++ において 参照渡し というよりも ポインタの値渡し に近いものがあります

#include <iostream>
#include <vector>

void output(const std::vector<int> &v){
  for(int e : v) std::cout << e << " ";
  std:: cout << "\n";
}

void f(std::vector<int> *v){
  v->pop_back();
}

void g(std::vector<int> *v){
  v = new std::vector<int>{1, 2, 3, 4};
}


int main() {
  std::vector<int> *a = new std::vector<int>{1, 2, 3};

  output(*a); /// 1 2 3

  f(a);

  output(*a); /// 1 2

  g(a);

  output(*a); // 1 2

}

ポインタの参照先は共通なのでオブジェクトへの操作は共有され, ポインタとなっている変数自体はコピーが渡されているだけなので, 書き換えても元の変数は変わりません

変数を使う際, ポインタとなっている変数 を指しているのか ポインタが指している先の変数 を指しているのかを C++ の場合はアロー演算子の有無によって明示的に切り替えていますが, JavaScript ではそういったものがないので (僕にとっては) 非常に混乱する元となりました

おわりに

主張として強い言葉を使ってしまいましたが, まぁ用語が被るのはよくあるので別にいいです (数学と日常で 以下 とか または とかが違ったりね)

けど, 切り替えられないの不便じゃないかなって思ったりはした (C++ が特殊説はあるけどこれに慣れているので...)

6
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?