web-dev-qa-db-ja.com

引数C ++ / Cの参照ポインタと逆参照ポインタ

わかりました、私が書く多くの関数でいつもこれに直面しています、どちらを使うべきですか?

void sth(int* a)
void sth(int& a)

Aが小さな変数であるか、aが大きなデータ構造であるという2つの別々の状況に関して、どちらが速いか。

実際のハードウェアとスタックプロセスに関連する深い回答をお願いします。

4
user209347

ほとんどのコンパイラは参照をポインタとして実装します。したがって、あなたの質問に対する深い答えは、2つの間のパフォーマンスの点で絶対的な違いはないということです。 (私が知る限り、エイリアシング分析は変更しません。)

そのステートメントを100%確実にしたい場合は、コンパイラの出力を調べてください。

struct Small {
    int s;
};
void foo(Small* s)
{
    s->s = 1;
}
void bar(Small& s)
{
    s.s = 1;
}

clang++ -O2でコンパイルし、アセンブリを保存します。

_Z3fooP5Small:                          # @_Z3fooP5Small
    .cfi_startproc
# BB#0:
    movl    $1, (%rdi)
    ret
_Z3barR5Small:                          # @_Z3barR5Small
    .cfi_startproc
# BB#0:
    movl    $1, (%rdi)
    ret

大きな構造体または非常に複雑な構造体でそれを試すことができます-問題ではありません。関数に渡すのはポインタだけです。

そうは言っても、2つの間にsemanticの違いがあります。最も重要なことは、プログラムに未定義の動作がない限り、参照を取得するオーバーロードは、有効なライブオブジェクトへの参照を確実に取得できるということです。ポインターのオーバーロードはそうではありません。

また、これらの2つの例でsに割り当てると、意味がまったく異なります。これは、最初の関数のポインターを置き換えます(つまり、指しているものは変更されないままですが、その関数内から到達できなくなります;呼び出し元は割り当ての影響を受けません)。
2番目に、渡されたオブジェクトに対して適切な代入演算子を呼び出します(呼び出し元から見える効果)。

したがって、選択は潜在的なパフォーマンスの違い(一般的にはなし)で行うのではなく、セマンティクスで行う必要があります。関数が実行できるようにする必要があること、および関数を呼び出すことができる方法によって、提供する必要があるオーバーロードが決まります。

13
Mat

int*int&の主な意味上の違いは、前者はNULLまたは初期化されていない値の受け渡しを許可し、後者は許可しないことです。したがって、ポインタを使用した関数の実装は次のようになります。

 void sth(int* a)
 {
     if(a==NULL)
     {
         // handle NULL case
     }
     else
     {
         // do something with *a
     }
 }

参照を使用する場合、関数内でのその特別なNULL処理を省略できます。

したがって、作成する関数が明示的にNULL値を入力として許可する必要がない場合は、int&を使用します。 このWikipediaのエントリ も参照してください。

2つの選択肢のどちらが速いかに基づいて決定を行うべきではないことに注意してください。あなたの最優先事項は正しいコードであるべきであり、この場合私が無視することができると期待するミクロの最適化ではありません。

10
Doc Brown

参照とポインタの最も重要な違いは、ポインタを介してオブジェクトを解放することは可能ですが、参照を介してオブジェクトを解放することはできないということです。

したがって、オブジェクトaのメソッドで引数bのポインター型の代わりに参照型を選択すると、aの所有権がbに転送されないことが通知されます。

(逆参照されたNULLをチートなしでメソッドへの参照として渡すことは不可能であるという一般的な考えは間違っています。オブジェクトを作成するほとんどのメソッド(たとえば、cloneまたはファクトリー)は、NULLかどうかにかかわらずポインタを返します。メソッドが新しく作成されたオブジェクトで参照を使用して呼び出したい場合は、ポインターを逆参照する必要があります。)

リファレンスとポインタは、同じ概念の2つの実装です:indirection(つまり、「何かについて話す代名詞 ")

マシンレベルではこれらは同じもの(インデックスメモリセル)であるため、パフォーマンスの違いはありません。言語レベルでの主な違いは、主に「明示的」で「変更可能」である点です。

  • ポインタの逆参照は明示的です:与えられたpa = &a;a.xpa->xと同じです
  • 参照の逆参照は暗黙的です。与えられたra = a;a.xra.xと同じです

変数へのアクセスが直接か間接かに関係なく、式の表現方法が変化しないため、式内の同じ構文により、参照がジェネリック関数でより適切になります。

  • ポインタは変更可能です:pa = &a1; ...; pa = &a2;または++paまたはpa[x]はすべて可能です
  • 参照は変更できません:ra = a1; ... ; ra= a2;実際にはa2の値をa1に割り当てます(したがって別のゲームをプレイします)

ポインターの可変性により、汎用イテレーターの実装により適しています。

これは、固定式レンチと適応式レンチについて話しているようなものです。形状が異なるため、特定のコンテキストに応じてユーザビリティを変更できます。しかし、ねじの観点からは、それらは単なるレンチです。

1