これらの概念のペア間の関係は何ですか?
概念の最初のペアでは、値型のオブジェクトは要素(データまたはプロシージャ)であり、参照型のオブジェクトであるように思えます要素のlocation(絶対または相対)です。
質問:
注意。 —付録で提供されている定義から明らかなように、プログラミングのコンテキストでの「値のセマンティクス」の意味はずれています。以下は、すべてを理解しようとする私の試みです。
情報はメモリ内のスペースに格納され、再利用できます。メモリには3つのスペースがあります。
それぞれの種類で、メモリ内に複数のスペースが存在する可能性があります。たとえば、複数の引数。それぞれメモリ内のスペース。
言語/ランタイム/プラットフォームには、これらのいずれかがある場合とない場合があります。たとえば、一部にはスタックがありません。配列や複合型がないものもあります。また、ヒープがないものもあります。ただし、それらにはすべて少なくともヒープまたはスタックがあります。
名前付き定数、リテラル、即値、またはl値とr値の違いについては触れません。
ほとんどの言語では、メモリ内のスペースに名前を付けます。これにより、使いやすくなります。メモリ内のこれらの名前付きスペースを「変数」と呼びます。
今後は、メモリ内のスペースに格納されている、変数によって名前が付けられた情報を変数の内容と呼びます。
変数の名前がランタイムに存在する場合と存在しない場合(リフレクションなど)があることにも注意する必要があります。また、変数の名前が存在する場合は、静的な型情報がランタイムに存在する場合と存在しない場合があります(タイプの消去など)。
さらに、名前付き変数のメモリ内の位置が変わる場合があります。
注意。 —私がここでコンテンツと呼ぶもの、他の著者は価値を呼びます。ラコスの値の定義を使用しているので、私はそれを値と呼びません。ただし、変数の内容は値であることに同意します。物理的価値。ラコスが話している価値は プラトニック 論理値。
タイプは、メモリレイアウトのセットです。インスタンスとしてメモリに実際に存在する特定のタイプの可能なメモリレイアウトのそれぞれを参照します。 メモリ内でインスタンスが重複する可能性があります。
これらのメモリレイアウトは、インスタンスを保持する変数の内容を定義します。 以下の「値のタイプと参照タイプ」を参照してください。
動的型付け言語では、変数の内容は任意の型にすることができます。
一方、静的型付け言語では、変数に型があり、この型は変数の可能な内容を指定します。
注意。 —一部の静的型付き言語では、変数を動的型として入力できます。つまり、変数の型は「変数の内容を調べて型を理解する」です。
複合型は、他の型から構築された型です。これはプリミティブ型には当てはまりません。
プリミティブ型と組み込み型を混同しないでください。それは、言語によって提供されるタイプのセットです。現在、多くの言語が複合型を提供しています。代わりに、プリミティブ型は言語の制約内で分割できません。
型のインスタンスを考えると、これらのインスタンスの同等性の概念に関心がある場合とない場合があります。つまり、等価性はタイプの仕様/要件の一部である場合とそうでない場合があります。
型が「値」の概念を持っている場合、私たちは平等についてのみ気にします。
値の概念を持つ型の場合、値はインスタンスの内容から派生します。むしろ、その内容は価値を表していると言えるでしょう。
ただし、内容は値ではありません。つまり、インスタンスの同等性は、メモリ内での同等の表現を意味するものではありません。これは、同じ値に対してメモリ内に複数の表現が存在する可能性があるためです。たとえば、一部のタイプではメモリ内の値を表す方法が複数あるため、正規化/正規化が必要になる(たとえば、文字列、日付、10進浮動小数点数)と考えてください。
これは、異なるタイプに格納された値が同じ値である、つまり等しい(たとえば、5が短整数に格納され、5が長整数に格納されている)と言える方法でもあります。
複合型を扱う場合、顕著な属性について話します。
John S. Lakos著の本Large-Scale C++ Volume I:Process and Architectureから:
値セマンティック型の顕著な属性は、オブジェクト自体の全体的な値に寄与する(通常は観察可能な)属性の1つです。
「値セマンティックタイプ」に到達します。
重要な属性のみがタイプの値の一部と見なされ、どの属性が重要であるかは、メモリ内の表現ではなく、そのタイプの仕様/要件によって決まります。
参照は変数であり、その内容はインスタンスではなくインスタンスを参照します。つまり、コンテンツは、インスタンスを直接含むのではなく、インスタンスが見つかったメモリ内の位置になります。
上記で定義したのは、C++のポインタです。ポインターと参照のC++の違いについては触れていません。
一部のプラットフォームでは、インスタンスを移動する可能性のあるガベージコレクタがあります。これが発生すると、ガベージコレクターもそれらへの参照を更新する必要があります。
構成により、参照があるインスタンスがある場合があります。
各変数はメモリ内にスペースがあるため、変数を別の変数に割り当てるとき(それらの型に互換性があると想定)、内容をコピーする必要があります。 下の「コピーのタイプ」を参照してください。
変数のタイプに互換性がない場合。変換が必要です。 1つの特殊なケースは、参照に割り当てる場合です。
場合によっては、変数が存在しなくなることがわかっています。たとえば、サブルーチンから戻るときのローカル変数はスコープ外になります。ローカル変数を返し、戻り値を別の変数に割り当てる場合、コンパイラーはそれをコピーせずに、代わりに移動することを選択する場合があります。ここに移動すると、変数で指定されたメモリ内のスペースが変更されます。
変数が存在しなくなったときにのみ移動が発生するため。引っ越しの心配はありません。
サブルーチンのパラメーターは変数です。サブルーチンを呼び出すと、パラメーターが割り当てられます。パラメータが参照型の場合、参照によってインスタンスを渡します。それ以外の場合は、値渡しです。そして、はい、それはコピーです。
浅いコピーは、変数の内容のコピーに限定されます。一方、ディープコピーは参照に従い、それらもコピーします。つまり、ディープコピーは参照に関して再帰的です。
インスタンスのコピーに関しては、これらのオプションだけではないことに注意してください。戻ってきます。
参照を含まないコンテンツの場合、浅いコピーは完全なコピーです。ただし、参照を含むコンテンツの場合、完全なコピーを取得するにはディープコピーが必要です。
インスタンスのメモリレイアウト全体のコピーである、完全なコピーとして理解します。全体をコピーしないと、不完全なコピーになります。メモリレイアウトに参照がなく、変数のコンテンツにのみ存在する場合、シャローコピーは完全なコピーです。それ以外の場合、浅いコピーは不完全なコピーです。
浅いコピーがデフォルトです。
注意。 —可変コンテンツはリソースへのハンドルである可能性があります。これは、ウィンドウオブジェクトへのハンドルやデータベーステーブルの行へのキーなど、外部リソースである可能性があります。また、配列のインデックスなどの内部リソースの場合もあります( Entity-Component-System を参照)。これらは上記で定義された参照ではありませんが、そのように考えることができます(ハンドルは論理参照であるのに対し、ポインターは物理参照であると言えます)。参照されるリソースがコピーされない場合、それらはインスタンスが互いに影響を与える手段を提供する可能性があります。 下の「3つのルール」を参照。 [〜#〜] raii [〜#〜] にも興味があるかもしれません。私の個人的な意見では、外部リソースへのハンドルを含む値のセマンティクスをアーカイブしようとすべきではありません。そうした場合、それらのリソースもコピーする必要があります。
C#言語リファレンス で見つかります:
値タイプの変数には、タイプのインスタンスが含まれています。これは、型のインスタンスへの参照を含む参照型の変数とは異なります。
参照型は、その型の変数がインスタンスへの参照であるような型です。 参照型のメモリレイアウトは、変数がインスタンスへの参照を保持することを定義します。
C++では、ポインタと参照のみが参照型です。ただし、他の言語では多くの参照型が見つかります。たとえば、Javaと.NETクラスは参照型です。ちなみに、C#構造体は値型です。
一方、値タイプは、そのタイプの変数が参照ではないタイプです。つまり、変数の内容はインスタンスです。
値の型と参照の型を値のセマンティック型と参照のセマンティック型と混同しないでください。また、値タイプとプリミティブタイプを混同しないでください。
さて、参照型の変数は参照なので。そして、浅いコピーがデフォルトです。参照タイプを割り当てると、デフォルトが上書きされない限り、不完全なコピーになります。
値の型の場合、割り当ては完全なコピーになります(参照が含まれる複合型ではない場合のみ)。参照 構造体に参照型のフィールドを含めることができる (C#)。
値セマンティック型は、コピーがインスタンスの独立性を提供するような型です。つまり、コピーの結果は、オリジナルを変更するために使用できません。 コピーの強調。これは参照を作成することではありません。
これは Alexis Gallagher’s Mutation game と一致します。
これを行うには、2つの簡単な方法があります。
ただし、一般に、不変ではないインスタンスのすべての部分をコピーするコピーを提供する必要があります。タイプが不変の場合は、浅いコピーで十分です。型に不変部分がない場合(かつそれが参照型または参照を含む値型である場合)、ディープコピーを提供する必要があります。一部の部分が不変で、一部が不変の場合は、可変部分の深いコピー(および不変部分の浅いコピー、それらの共有)を実行することで、値のセマンティクスをアーカイブできます。ちなみに、これは浅いコピーでも深いコピーでもありませんが、混合です。
注意。 — Bjarne Stroustrupは Programming:Principles and Practice Using C++ で値のセマンティクスを定義するときに、深いコピーと浅いコピーのみを考慮します。
不変の参照型のフィールドのみを含む参照型がある場合。次に、その参照をコピーするだけで十分です。不変のインスタンスをコピーする必要はありません。次に、その参照を新しい参照と交換して変異操作を実装します。これはコピーオンライトです。
本からドメイン駆動設計:ソフトウェアの心臓部における複雑性への取り組み(「値オブジェクト」という用語を作り出したエリックエバンス):
オブジェクトは、継続性とアイデンティティを持つ何かを表しますか?異なる状態を介して、または異なる実装全体でさえ追跡されるものですか?それとも何か他の状態を説明する属性ですか?これは、ENTITYとVALUE OBJECTの基本的な違いです。
エバンスはまた、値の意味論の懸念を持っていました:
VALUE OBJECTがどのインスタンスにあるかは関係ありません。この制約の欠如により、設計の簡素化やパフォーマンスの最適化に使用できる設計の自由が得られます。これには、コピー、共有、および不変性に関する選択が含まれます。
私たちは同じ定義を見ており、他の作者によってエコーされた価値セマンティクスについての同じ懸念があります。
Martin Fowlerらの本Patterns of Enterprise Application Architectureから:
参照オブジェクトと値オブジェクトの主な違いは、それらが同等性を処理する方法にあります。参照オブジェクトは、同一性の基礎として同一性を使用します[…]。値オブジェクトは、クラス内のフィールド値に等しいという概念を基にしています。したがって、2つの日付オブジェクトは、それらの日、月、および年の値が同じである場合、同じである可能性があります。 […]ほとんどの言語には、値オブジェクト用の特別な機能はありません。これらの場合に値オブジェクトを適切に機能させるには、値オブジェクトを不変にすることをお勧めします。つまり、一度作成されたフィールドはどれも変更されません。これは、エイリアスのバグを回避するためです。 2つのオブジェクトが同じ値オブジェクトを共有し、所有者の1人がその値を変更すると、エイリアスのバグが発生します。
Value Object も参照してください。
エバンスは、価値のあるオブジェクトであり、変更可能であり、アイデンティティを持つエンティティでもあることに注意してください。
それ以上に、エヴァンスはサービスについても説明します。サービスとは、価値がなく、動作に関するオブジェクトです。スレッド構造の多くはサービスです。たとえば、読み取り/書き込みロック。読み書きロックは値ではありません。
注意。 —私は、値オブジェクトは値のセマンティクスを意味せず、値の平等のみを意味すると言っています。ただし、値のセマンティクスは、値オブジェクトの望ましい機能です。値のセマンティクスのない値オブジェクトは設計が不十分であると言えます。
3つのルール
これはC++に固有です。
値のセマンティクスが必要で、参照型フィールドのない値型があるとします。これには、デフォルトの浅いコピーで十分です。
ここで、型に参照型フィールドを追加するとします。したがって、浅いコピーでは、参照型の同じインスタンスを指すフィールドを持つ2つのインスタンスが生成されます。
浅いコピーを回避するには、代入演算子をオーバーライドし、深いコピーを実装する必要があります。ただし、既存の変数に割り当てずに新しい変数を初期化する場合、代入演算子は呼び出されませんが、代わりにコピーコンストラクターが呼び出されます(デフォルトは浅いコピーです)。したがって、コピーコンストラクタもオーバーライドする必要があります。
デフォルトのデストラクタで同様の問題が発生します。参考にはなりません。つまり、それは深い破壊を行いません。つまり、参照型フィールドのインスタンスがリークすることになります。したがって、デフォルトのデストラクタもオーバーライドする必要があります。
したがって、代入演算子、コピーコンストラクター、およびデストラクターをオーバーライドする必要があります。 これはほとんどの言語では不可能です。
つのルール も参照してください。
値のセマンティクスを定義するために、参照またはポインタの概念を要求するべきではありません。これらの概念を持たない言語でも、値のセマンティクスを使用できます。
話し合う必要がある値オブジェクトに関連する別の概念があります: データ転送オブジェクト 。 DTOは境界を越えることを目的としています。彼らは別のプロセス、さらには別のマシンに行く可能性があります。彼らはそうしないかもしれません。これらの境界を越える場合、参照は機能しません。したがって、DTOは参照を回避する必要があります。
DTOには動作がなく、値のセマンティクスが必要です。
DTOはしばしば値オブジェクトと混同されます。マーティン・ファウラー:
ドメインオブジェクト自体を送信することはできません。ドメインオブジェクトは、きめ細かいローカルオブジェクト間参照のWebに関連付けられているためです。したがって、クライアントが必要とするすべてのデータを取得して、転送のために特定のオブジェクトにバンドルします。このため、データ転送オブジェクトという用語を使用します。 (エンタープライズの多くの人々Javaコミュニティはこれに値オブジェクトという用語を使用していますが、これは値オブジェクトという用語の他の意味との衝突を引き起こします)。
オブジェクトの定義に戻ると(Grady Boochによると)、オブジェクトにはアイデンティティ(および状態と動作があり、どれもない場合があります)があることがわかります。ただし、この定義は無視しており、オブジェクトはクラスのインスタンスであると述べています。
さらに、名前値オブジェクトは、EvansがJavaで作業していたため、カスタム値型を定義できなかったという事実に影響されていると私は主張します。繰り返しますが、Javaの値オブジェクトは参照型です。
値のセマンティクスのもう1つの引数はスレッドセーフです。
参照を渡す場合、たとえconst参照であっても、裏で別のスレッドによって変更される可能性がある場合は、問題が発生することに注意してください。したがって、参照は不変の型またはスレッドセーフな型でなければなりません。
値タイプのオブジェクトは値オブジェクトですか?
値オブジェクトは、値タイプまたは参照タイプにすることができます。
参照タイプのオブジェクトは参照オブジェクトですか?
参照型のインスタンスは、同等性をオーバーライドしない限り、参照オブジェクトになります。
値タイプのオブジェクトは値のセマンティクスを持っていますか?
参照型フィールドがない場合、またはデフォルトのコピーをオーバーライドして値のセマンティクスを提供する場合。
参照タイプのオブジェクトは参照セマンティクスを持っていますか?
不変ではなく、デフォルトのコピーをオーバーライドして値のセマンティクスを提供しない場合。
List のこのテンプレートバージョンには、ジェネリックイテレータとvalue semanticsが含まれており、ジェネリックデータを格納します。値のセマンティクスは、 List がインスタンス化されたオブジェクトではなく、オブジェクトへのポインタを格納することを意味します。挿入操作中、 List は、ポインターを格納する代わりにデータ値のコピーを格納します。値のセマンティクスを持つコンテナーを使用すると、アプリケーションは小さなオブジェクトや組み込みの型を簡単に管理できますが、多くのアプリケーションはオブジェクトのコピーによるオーバーヘッドを許容できません。
–ポールアンダーソン、ゲイルアンダーソン– C++とオブジェクト指向デザインのナビゲーション
STLコンテナーは値のセマンティックです。タスクオブジェクトがSTLコンテナーに追加されると、タスクオブジェクトのアロケーターとコピーコンストラクターが呼び出され、元のオブジェクトが複製されます。同様に、タスクオブジェクトがSTLコンテナーから削除されると、タスクオブジェクトのデアロケーターが呼び出されてコピーが削除されます。値のセマンティクスは、特にプロデューサーとコンシューマーがタスクをキューに頻繁に追加したりキューから削除したりする場合に、パフォーマンスの問題になることがあります。
– Ted Yuan – C++プロデューサー-コンシューマー同時実行テンプレートライブラリ
値ごとのオブジェクトのValueSemanticsは、オブジェクト間で値をコピーすることによって保持されます。参照によるオブジェクトのValueSemanticsは、CopyOnWriteメカニズムを使用して保持されます。話はそこで終わったといつも思っていました。 ValueObjectsは単にValueSemanticsを保持するオブジェクトですか、それともそれ以上のものですか?
– PhilGoodwin – 値オブジェクトは変更可能
浅いコピーを提供する型(ポインターや参照など)は、ポインターセマンティクスまたは参照セマンティクス(アドレスをコピーする)を持つと言われます。ディープコピーを提供する型( string や vector など)は、値のセマンティクスを持っている(ポイントされた値をコピーする)と言われています。ユーザーの観点からは、値のセマンティクスを持つ型は、ポインターが含まれていないかのように動作し、コピーできる値のみが動作します。値のセマンティクスを持つ型の考え方の1つは、コピーに関する限り、「整数のように機能する」というものです。
– Bjarne Stroustrup – プログラミング:C++を使用した原則と実践
(…)型が値のセマンティックになる可能性があります。これは、1つの非常に重要なプロパティをtrueに保ち、指定された型の2つのオブジェクトが今日同じ値を持ち、同じ顕著な操作に適用される場合です(顕著なとは、操作を意味します)これは、モデルとして使用しているプロセスの外部に存在するプラトニックタイプを近似することを目的としています)。その操作が両方のオブジェクトに適用されると、オブジェクトは再び同じ値を持つか、まったく同じ値になります。これは、値のセマンティクス。
これを別の言い方をすると、2つのオブジェクトが同じ値を持っている場合、それらが同じ値を持たなくなる一連の顕著な操作は存在しません。
–ジョンラコス– ジョンラコスへのインタビュー
値のセマンティクスは、変数の値の独立性を保証することになります。
そして、独立は構造的なことを意味しません。私たちが話していることは、あることが別のことに影響する可能性があるということです。したがって、変数の値、つまり値のセマンティックタイプを持つ変数を変更する唯一の方法が変数自体を使用する場合、型は値のセマンティクスを持ちます。変数の値を変更する唯一の方法が変数自体を使用する場合、それはセマンティックタイプの変数です。
(…)
他のものによる副作用の影響を受けない場合、タイプは値のセマンティックです。他のものに副作用を及ぼさないことが保証されているのでなければ。
– Alexis Gallagher – 値の意味(値のタイプではありません!)
これらの概念は非常に密接に関連しており、すべて同じことについて語っています。
より抽象的で一般的なのは意味論です。
プログラミングに関係のない日常の生活の例をわざと使用しました。
この概念をOOPに適用すると、値オブジェクトと参照オブジェクトの概念が得られます。整数オブジェクトの例を見てください。ほとんどの言語では、整数は値オブジェクトです:2つの整数オブジェクトの場合同じ値の場合、2つの異なるオブジェクトであっても、等しいと見なされます。参照オブジェクトを持つこともできます。ここでは、値自体は関係なく、オブジェクト自体にのみ関係があります。参照オブジェクトを変更した場合、新しい値は、参照が使用されているすべての場所で即座に認識されます。C++には値オブジェクトがありますが、オブジェクトまたは参照へのポインターを使用して、いつでも参照オブジェクトを作成できます。
最後に、参照型と値型は、型に適用することによるセマンティックの特殊化です。これは、型付き言語にのみ関連する概念です。たとえばC#では、クラスは参照型であり、構造体は値型です。つまり、すべてのオブジェクトは、タイプの場合、セマンティックとしてタイプで作成されます。
最後に、値と参照はOOP以外の言語でもパラメーターの受け渡しのコンテキストで関連していることに言及する価値があります。
これらの用語を見る場合、これらの用語が過負荷であり、さまざまなレベルの抽象化であることを理解する必要があります。
私たちはこれらの用語の広義で常識的な使い方をしており、DDD(ドメイン駆動設計)によって定義されたこれらの用語のいくつかがあり、さらにさまざまなプログラミング言語によって定義されています。
プログラミング言語による定義は、それぞれの言語に固有で正確です。たとえば、Javaには、値タイプであるプリミティブタイプがあり、値のセマンティクスが示されています。など。これまで、ユーザー定義の値タイプはありませんでしたが、次のような不変オブジェクトタイプの代わりに使用されています。文字列クラスの場合—ただし、すべてのオブジェクトには、文字列やその他の不変の型を含め、(ポインター等価比較によって)監視できる場所があります。
DDDは、値オブジェクトをIDのないオブジェクトとして定義しますが、値タイプを定義しません。
C#はユーザー定義の値型を提供しますが、これらは変更可能であり、それらの場所を確認できます。
(C++は独自の規約とルールを備えた他の完全なワーム缶です。)
参照型は、それ自体「参照オブジェクト」ではなく、オブジェクトへの参照を使用します—これは単なる用語だと思います。
上記のようにあなたが働いているコンテキストを法として、私はあなたの他の考えに同意します。
@ Christophe's と @ Theraotの優れた答え について議論し、 Bjarne Stroustrup's と Phil Goodwinの定義 からインスピレーションを得た後、ついに次の一連の定義に到達しました。これは、以前の著者とは近いですが少し異なる(より一般的な)ものです。
値のセマンティクスのための十分な条件:
参照セマンティクスの十分な条件:
コメントでフィードバックをお寄せください。
C++での割り当てによるメモリレイアウト:
int i{3}; // i:3
int j{i}; // i:3 j:3 (copy of i: j)
int* p{&i}; // i:3 p:&i (alias of i: *p)
int* q{p}; // i:3 p:&i q:&i (copy of p: q, alias of i: *q)
int* r{new int{*p}}; // i:3 p:&i *r:3 r:_ (copy of i: *r)
int** s{&p}; // i:3 p:&i s:&p (alias of p: *s)
int** t{s}; // i:3 p:&i s:&p t:&p (copy of s: t, alias of p: *t)
int** u{new int*{*s}}; // i:3 p:&i s:&p *u:&p u:_ (copy of s: *u, alias of p: **u)
ここで、i
とj
は値の意味関係にあり、p
とq
は参照意味関係にあり、p
とr
は値の意味関係にあり、s
とt
は参照意味関係にあり、s
とu
は参照意味関係にあります。