なぜこれほど多くの言語が値渡しされるのですか?


36

Cのような明示的なポインター操作がある言語でさえ、常に値で渡されます(参照で渡すことができますが、これはデフォルトの動作ではありません)。

これの利点は何ですか、なぜ多くの言語が値で渡されるのなぜ他の言語は参照で渡されるのですか?(私は確信していませんが、Haskellが参照渡しであることを理解しています)。


5
void acceptEntireProgrammingLanguageByValue(C++);
トーマスエディング

4
さらに悪いことかもしれません。一部の古い言語では、名前による呼び出し
-hugomg

25
実際、Cでは参照渡しはできません。valueポインター渡すことができます。これは、参照渡しと非常に似ていますが、同じものではありません。ただし、C ++では、参照渡しできます。
メイソンウィーラー

@Masonウィーラーあなたは、おかげでもっと手の込んだか私は、普通のプログラマではないC / C ++第一人者だとしてあなたの文は私にクリアされていないbeacuase、いくつかのリンクを追加することができます
Betlista

1
@Betlista:参照渡しすると、あなたはこのように見えることをスワップルーチンを記述することができます。temp := a; a := b; b := temp;そして、それを返すとき、の値aとがbスワップされます。Cでそれを行う方法はありません。あなたはへのポインタを渡す必要がありabし、swapルーチンは、彼らが指す値に基づいて行動しなければなりません。
メイソンウィーラー

回答:


58

メソッド/関数のパラメーターを誤って変更することはできないため、値渡しは参照渡しよりも安全です。これにより、関数に与える変数を心配する必要がないため、言語の使用が簡単になります。あなたはそれらが変更されないことを知っています、そして、これはしばしばあなたが期待するものです。

あなたはしかし、もしたいパラメータを変更するには、このクリア(ポインタでパスを)作るために、いくつかの明示的な操作を持っている必要があります。これにより、すべての呼び出し元が呼び出しをわずかに異なるように強制し(&variableCでは)、変数パラメーターが変更される可能性があることを明示します。

そのため、明示的にマークされていない限り、関数は変数パラメーターを変更しないと仮定できます(ポインターを渡すように要求することにより)。これは、代替手段よりも安全でクリーンなソリューションです。明確にできないと言わない限り、すべてがパラメーターを変更できると想定します。


4
ポインタに減衰する+1配列は顕著な例外を示しますが、全体的な説明は良好です。
-dasblinkenlight

6
@dasblinkenlight:配列はCの痛いところです:(
Matthieu M.

55

値渡しと参照渡しは、かなり前にパラメータ受け渡しモードと間違われた実装手法です。

最初は、FORTRANがありました。サブルーチンはパラメーターを変更する必要があり、複数のパラメーター渡しモードを許可するには計算サイクルが高すぎたため、FORTRANには参照による呼び出ししかありませんでした。さらに、FORTRANが最初に定義されたときのプログラミングについては十分ではありませんでした。

ALGOLは、名前による呼び出しと値による呼び出しを考案しました。値渡しは、変更されるはずのないもの(入力パラメーター)のためのものでした。名前による呼び出しは出力パラメーター用でした。名前による呼び出しは大きな障害であることが判明し、ALGOL 68はそれを落としました。

PASCALは、値による呼び出しと参照による呼び出しを提供しました。プログラマーがコンパイラーに、参照によって大きなオブジェクト(通常は配列)を渡していること、パラメータースタックを吹き飛ばすことをコンパイラーに伝える方法を提供しませんでしたが、オブジェクトは変更すべきではありません。

PASCALは、言語設計用語集へのポインターを追加しました。

Cは、メモリ内の任意のオブジェクトへのポインタを返すkludge演算子を定義することにより、値による呼び出しとシミュレーションによる参照による呼び出しを提供しました。

後の言語がCをコピーしたのは、主にデザイナーが他に何も見たことがないからです。これがおそらく、値渡しが非常に人気がある理由です。

C ++は、参照による呼び出しを提供するために、Cクラッジの上にクラッジを追加しました。

現在、値による呼び出し対参照による呼び出し対ポインターによる処理の直接的な結果として、CおよびC ++(プログラマー)はconstポインターとconstへのポインター(読み取り専用)でひどい頭痛を抱えています。オブジェクト。

エイダはこの悪夢全体をどうにかして避けました。

Adaには、明示的な値による呼び出しと参照による呼び出しがありません。むしろ、Adaにはinパラメーター(読み取りは可能ですが、書き込みはできません)、outパラメーター(読み取り前に書き込みが必要)、in outパラメーターは任意の順序で読み取りと書き込みが可能です。コンパイラーは、特定のパラメーターを値で渡すか参照で渡すかを決定します。これはプログラマーに対して透過的です。


1
+1。これは私がここでこれまで見てきた唯一の答えであり、実際に質問に答え、理にかなっており、親FPの暴言の透明な言い訳としてそれを使用しません。
メイソンウィーラー

11
「デザイナーが他に何も見たことがなかったことが主な理由で、後の言語がCをコピーした」ための+1 0が前に付いた8進定数を持つ新しい言語を見るたびに、少し内側で死にます。
librik

4
これは、プログラミングの聖書の最初の文である可能性があります In the beginning, there was FORTRAN

1
ADAに関して、グローバル変数がin / outパラメーターに渡されると、言語はネストされた呼び出しによる検査または変更を防ぎますか?そうでない場合、in / outパラメーターとrefパラメーターの間に明確な違いがあると思います。個人的には、言語が「in / out / in + out」と「by value / by ref / don't-care」のすべての組み合わせを明示的にサポートするようにしたいと思います実装の柔軟性が最大限になります(プログラマーの意図に沿ったものである限り)。
supercat 14年

2
@Neikosまた、0プレフィックスがベース10以外のすべてのプレフィックスと一致しないという事実もあります。導入時に8進数が唯一の代替ベースである場合、C(およびほとんどのソース互換言語、たとえばC ++、Objective C)で多少許されるかもしれませんが、より現代的な言語が両方0xを使用0し、最初から見た目が悪くなります考え抜いた。
8ビットツリー

13

参照渡しでは、意図しない動作を引き起こし始めたときにトレースするのが非常に難しい、またはほとんど不可能な非常に微妙な意図しない副作用が可能になります。

値、特にfinalstaticまたはconst入力パラメーターを渡すと、このクラスのバグがすべてなくなります。

不変言語はさらに決定論的であり、関数に何が入力され、何が関数から出力されると予想されるのかを推論および理解するのが簡単です。


7
これは、すべてが参照としてデフォルトになっている「古代の」VBコードをデバッグしようとした後にわかるものの1つです。
jfrankcarr

たぶん私のバックグラウンドが違うだけかもしれませんが、あなたが言っている「トレースするのが不可能に近い、非常に難しい非常に微妙な意図しない副作用」がわからないのです。私はPascalに慣れていますが、ここではby-valueがデフォルトですが、明示的にパラメーターをマークすることでby-referenceを使用できます(基本的にはC#と同じモデルです)。by-refがデフォルトである「古代VB」でどのように問題になるかはわかりますが、by-refがオプトインの場合は、作成中に考えさせられます。
メイソンウィーラー

4
@MasonWheelerあなたの後ろにやってくる初心者はどうですか?それを理解せずにあなたがやったことをコピーして、間接的にその状態を間接的に操作し始めると、現実の世界は常に起こります。間接性が少ないのは良いことです。不変が最適です。

1
@MasonWheeler Swap一時変数を使用しないハックのような単純なエイリアスの例は、それ自体は問題ではないかもしれませんが、より複雑な例をデバッグするのがどれほど難しいかを示しています。
マークハード

1
@MasonWheeler:「変数はありません;定数はありません」という言葉につながったFORTRANの古典的な例は、渡されたパラメーターを変更する関数に3.0のような浮動小数点定数を渡すことです。関数に渡された浮動小数点定数の場合、システムは適切な値で初期化され、関数に渡すことができる「隠された」変数を作成します。関数がパラメータに1.0を追加すると、プログラム内の3.0の任意のサブセットが突然4.0になります。
-supercat

8

なぜこれほど多くの言語が値渡しされるのですか?

大きなプログラムを小さなサブルーチンに分割することのポイントは、サブルーチンについて独立して推論できることです。参照渡しにより、このプロパティが壊れます。(共有可変状態も同様です。)

Cのような明示的なポインター操作がある言語でさえ、常に値で渡されます(参照で渡すことができますが、これはデフォルトの動作ではありません)。

実際、Cは常に値渡しであり、参照渡しではありません。何かのアドレスを取得してそのアドレスを渡すことができますが、アドレスは引き続き値で渡されます。

これの利点は何ですか、なぜ多くの言語が値で渡されるのなぜ他の言語は参照で渡されるのですか?

参照渡しを使用する主な理由は2つあります。

  1. 複数の戻り値のシミュレーション
  2. 効率

個人的に、私は#1は偽物だと思います:それはほとんどの場合、悪いAPIや言語設計の言い訳です:

  1. 複数の戻り値が必要な場合は、それらをシミュレートせずに、それらをサポートする言語を使用してください。
  2. タプルなどの軽量データ構造にパッケージ化することにより、複数の戻り値をシミュレートすることもできます。これは、言語がパターンマッチングまたはバインドの破壊をサポートしている場合に特に有効です。例:Ruby:

    def foo
      # This is actually just a single return value, an array: [1, 2, 3]
      return 1, 2, 3
    end
    
    # Ruby supports destructuring bind for arrays: a, b, c = [1, 2, 3]
    one, two, three = foo
    
  3. 多くの場合、複数の戻り値も必要ありません。たとえば、よくあるパターンの1つは、サブルーチンがエラーコードを返し、実際の結果が参照によって書き戻されることです。代わりに、エラーが予期しない場合に例外をスローするEither<Exception, T>か、エラーが予想される場合に返す必要があります。別のパターンは、操作が成功したかどうかを示すブール値を返し、参照によって実際の結果を返すことです。繰り返しますが、失敗が予想外の場合、代わりに例外をスローする必要があります。失敗が予想される場合、たとえば、辞書で値を検索する場合、Maybe<T>代わりにaを返す必要があります。

値をコピーする必要がないため、参照渡しの方が値渡しよりも効率的です。

(私は確信していませんが、Haskellが参照渡しであることを理解しています)。

いいえ、Haskellは参照渡しではありません。また、値渡しではありません。参照渡しと値渡しはどちらも厳密な評価戦略ですが、Haskellは厳密ではありません。

実際には、Haskellの仕様が指定されていない任意の特定の評価戦略を。ほとんどのHakell実装は、名前による呼び出しと必要な呼び出し(メモ化を伴う名前による呼び出しの変形)の混合を使用しますが、標準ではこれを強制していません。

厳密な言語であっても、関数型言語の参照渡しと値渡しを区別することは意味をなさないことに注意してください。これらの違いは、参照を変更した場合にのみ観察できるためです。したがって、実装者は、言語のセマンティクスを損なうことなく、2つの間で自由に選択できます。


9
「複数の戻り値が必要な場合、それらをシミュレートせずに、それらをサポートする言語を使用してください。」これは少し奇妙なことです。基本的にあなたの言語は、あなたが必要なすべてを行うことができれば」と言っていますが、一つの特徴は、おそらくあなたのコードの1%未満に必要があること-しかし、あなたはまだでしょうその1%のためにそれを必要とする-で行うことができません特にクリーンな方法では、あなたの言語はあなたのプロジェクトにとって十分ではないので、すべてを別の言語で書き直すべきです。」申し訳ありませんが、それはとんでもないことです。
メイソンウィーラー

+1私はこの点に完全に同意します。大きなプログラムを小さなサブルーチンに分割することのポイントは、サブルーチンについて独立して推論できることです。参照渡しにより、このプロパティが破損します。(共有可変状態と同様)
レミ

3

言語の呼び出しモデル、引数のタイプ、および言語のメモリモデルに応じて、異なる動作があります。

単純なネイティブ型では、値で渡すことにより、レジスタで値を渡すことができます。メモリから値をロードしたり、保存したりする必要がないため、これは非常に高速です。オブジェクトの呼び出し元のコピーを台無しにするリスクを冒すことなく、呼び出し先が引数を使用した後、引数が使用するメモリを再利用するだけで、同様の最適化も可能です。引数が一時オブジェクトである場合、おそらくそうすることで完全なコピーを保存するでしょう(C ++ 11は、新しい種類の参照とその移動セマンティクスにより、この種の最適化をさらに明確にします)。

多くのオブジェクト指向言語(このコンテキストではC ++は例外です)では、値によってオブジェクトを渡すことはできません。参照渡しすることを強制されます。これにより、コードはデフォルトでポリモーフィックになり、OOに適切なインスタンスの概念とよりインラインになります。また、値で渡したい場合は、そのようなアクションを生成したパフォーマンスのコストを認識して、自分でコピーを作成する必要があります。この場合、言語は、最高のパフォーマンスを提供する可能性が高いアプローチを選択します。

関数型言語の場合、値または参照による受け渡しは単に最適化の問題だと思います。そのような言語の関数は純粋で副作用がないため、速度を除いて値をコピーする理由は本当にありません。そのような言語は同じ値を持つオブジェクトの同じコピーを共有することがよくあると確信しています。これは、pass by(const)参照セマンティックを使用した場合にのみ利用可能です。Pythonは、整数と一般的な文字列(メソッドやクラス名など)にもこのトリックを使用し、整数と文字列がPythonで定数オブジェクトである理由を説明しました。また、これにより、たとえばコンテンツの比較の代わりにポインターの比較を許可し、一部の内部データの遅延評価を行うことにより、コードを再び最適化することができます。


2

式を値で渡すことができます。それは自然なことです。(一時的な)参照による表現の受け渡しは...奇妙です。


1
また、一時的な参照で式を渡すと、(ステートフル言語で)喜んで変更したときに(単なる一時的なものであるため)悪いバグにつながる可能性がありますが、実際に変数を渡すと逆効果になり、fooを渡すようない回避策を考案する必要がありますfooの代わりに+0。
ハービー

2

参照渡しの場合、グローバルのすべての問題(つまり、スコープと意図しない副作用)を使用して、事実上常にグローバル値を操作しています。

参照は、グローバルと同じように有益な場合がありますが、最初の選択ではありません。


3
私はあなたが最初の文の参照渡しを意味すると思いますか?
jk。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.