最初に、値渡しと参照渡しの意味に戻る必要があります。
JavaやSMLのような言語の場合、変数の値をコピーするのと同じように、値による受け渡しは単純です(参照による受け渡しはありません)。すべての変数は単なるスカラーであり、組み込みのコピーセマンティクスがあるため、算術としてカウントされます。 C ++、または「参照」(名前と構文が異なるポインター)で入力します。
Cでは、スカラー型とユーザー定義型があります。
- スカラーには、コピーされる数値または抽象値(ポインターは数値ではなく、抽象値があります)があります。
- 集約型には、初期化されている可能性のあるすべてのメンバーがコピーされています。
- 製品タイプ(配列と構造体)の場合:再帰的に、構造体のすべてのメンバーと配列の要素がコピーされます(C関数の構文では、値で配列を直接渡すことはできません。構造体の配列メンバーのみが可能ですが、詳細です。 )。
- 合計タイプ(ユニオン)の場合:「アクティブメンバー」の値が保持されます。明らかに、すべてのメンバーを初期化できるわけではないため、メンバーごとのコピーは適切ではありません。
C ++では、ユーザー定義型にユーザー定義のコピーセマンティックを含めることができます。これにより、リソースの所有権と「ディープコピー」操作を持つオブジェクトを使用して、真に「オブジェクト指向」プログラミングが可能になります。このような場合、コピー操作は、実際にはほとんど任意の操作を実行できる関数の呼び出しです。
C ++としてコンパイルされたC構造体の場合、「コピー」は、コンパイラーによって暗黙的に生成されるユーザー定義のコピー操作(コンストラクターまたは代入演算子のいずれか)の呼び出しとして定義されます。これは、C / C ++共通サブセットプログラムのセマンティクスがCとC ++で異なることを意味します。Cでは集約タイプ全体がコピーされ、C ++では暗黙的に生成されたコピー関数が呼び出されて各メンバーをコピーします。最終結果として、どちらの場合も各メンバーがコピーされます。
(ユニオン内の構造体がコピーされる場合は、例外があると思います。)
したがって、クラス型の場合、新しいインスタンスを作成する唯一の方法(ユニオンコピーの外)は、コンストラクタを使用することです(簡単なコンパイラで生成されたコンストラクタがある場合でも)。
単項演算子を介して右辺値のアドレスを取得することはできませんが、&
右辺値オブジェクトがないことを意味するわけではありません。そしてオブジェクトは、定義により、アドレスを持っています。また、そのアドレスは構文構造によっても表されます。クラス型のオブジェクトはコンストラクターによってのみ作成でき、this
ポインターを持っています。しかし、単純な型の場合、ユーザーが作成したコンストラクターがないためthis
、コピーが構築されて名前が付けられるまで配置する場所がありません。
スカラー型の場合、オブジェクトの値はオブジェクトの右辺値であり、オブジェクトに格納される純粋な数学的値です。
クラス型の場合、オブジェクトの値の唯一の概念は、オブジェクトの別のコピーであり、これはコピーコンストラクター、つまり実際の関数によってのみ作成できます(単純な型の場合、関数は非常に単純ですが、これらは場合によってはコンストラクタを呼び出さずに作成されます)。つまり、objectの値は、実行によるグローバルプログラム状態の変更の結果です。数学的にはアクセスしません。
したがって、値による受け渡しは実際には重要ではありません。コピーコンストラクタコールによる受け渡しですが、それほど美しくありません。コピーコンストラクターは、オブジェクトタイプの適切なセマンティクスに従って、内部不変式(組み込みのC ++プロパティではなく、抽象的なユーザープロパティ)を考慮して、適切な「コピー」操作を実行することが期待されています。
クラスオブジェクトの値渡しとは、次のことを意味します。
- 別のインスタンスを作成する
- 次に、呼び出された関数をそのインスタンスに作用させます。
この問題は、コピー自体がアドレスを持つオブジェクトであるかどうかとは関係がないことに注意してください。すべての関数パラメーターはオブジェクトであり、(言語セマンティックレベルで)アドレスを持っています。
問題は次のいずれかです。
- コピーは、で初期化された新しいオブジェクトですスカラーと同様に、元のオブジェクトの純粋な数学的値(真の純粋な右辺値)でです。
- または、クラスと同様に、コピーは元のオブジェクトの値です。
自明なクラス型の場合でも、オリジナルのメンバーコピーのメンバーを定義できるため、コピー操作(コピーコンストラクターと代入)の自明性のため、オリジナルの純粋な右辺値を定義できます。任意の特別なユーザー関数ではそうではありません:オリジナルの値は構築されたコピーでなければなりません。
クラスオブジェクトは呼び出し元が作成する必要があります。コンストラクターは正式にthis
ポインターを持っていますが、ここでは形式は関係ありません。すべてのオブジェクトは正式にアドレスを持っていますが、実際に非純粋にローカルな方法で使用されるアドレスのみを取得するオブジェクト(*&i = 1;
純粋にローカルでのアドレスの使用とは異なります)だけが明確に定義されている必要があります住所。
オブジェクトは、これら2つの別々にコンパイルされた関数の両方でアドレスを持っているように見える必要がある場合、絶対にアドレスで渡される必要があります。
void callee(int &i) {
something(&i);
}
void caller() {
int i;
callee(i);
something(&i);
}
ここでsomething(address)
、純粋な関数やマクロ、またはprintf("%p",arg)
アドレスを格納できない、または別のエンティティと通信できないもの(など)であっても、アドレスは一意のオブジェクトに対して明確に定義する必要があるため、アドレスで渡す必要があります。int
ユニークを有します身元。
外部関数に渡されるアドレスに関して、外部関数が「純粋」であるかどうかはわかりません。
ここでは潜在的な非自明なコンストラクタやデストラクタのいずれかのアドレスの実際の使用のために、発信者側は、おそらく安全な、単純なルートを取った理由であると、呼び出し元のオブジェクトにアイデンティティを与えるとして、そのアドレスを渡すことが可能必ず施工後のコンストラクタで、そのアドレスのいずれかの非自明な使用、ということとデストラクタでは一貫している:this
オブジェクトの存在の上に同じように表示される必要があります。
自明ではないコンストラクタやデストラクタは、他の関数と同様に、this
自明でないものを持ついくつかのオブジェクトがそうでなくても、値に対する一貫性を必要とする方法でポインタを使用できます。
struct file_handler { // don't use that class!
file_handler () { this->fileno = -1; }
file_handler (int f) { this->fileno = f; }
file_handler (const file_handler& rhs) {
if (this->fileno != -1)
this->fileno = dup(rhs.fileno);
else
this->fileno = -1;
}
~file_handler () {
if (this->fileno != -1)
close(this->fileno);
}
file_handler &operator= (const file_handler& rhs);
};
その場合、ポインターの明示的な使用(明示的な構文this->
)に関係なく、オブジェクトIDは無関係であることに注意してください。コンパイラーは、オブジェクトをビット単位でコピーして移動し、「コピー削除」を行うことができます。これはthis
、特別なメンバー関数での使用の「純度」のレベルに基づいています(アドレスはエスケープしません)。
ただし、純正度は標準宣言レベルで使用可能な属性ではないため(インライン関数以外の宣言に純正度の説明を追加するコンパイラー拡張が存在するため)、使用できない可能性があるコードの純正度に基づいてABIを定義することはできません(コードは、インラインでなく、分析に使用できない場合があります)。
純度は「確かに純粋」または「不純または未知」として測定されます。共通基盤、つまりセマンティクスの上限(実際には最大)、またはLCM(最小公倍数)は「不明」です。したがって、ABIは未知数で解決します。
概要:
- 一部の構文では、オブジェクトIDを定義するためにコンパイラーが必要です。
- ABIはプログラムのクラスの観点から定義されており、最適化される可能性のある特定のケースではありません。
可能な将来の仕事:
純粋性注釈は、一般化および標準化するのに十分役立ちますか?