ポインタとは C++ のポインター変数と参照変数の違いは何ですか?




c++ ポインタとは (24)

一般的な意見とは異なり、NULLの参照を持つことは可能です。

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

確かに、リファレンスで行うことははるかに難しいですが、それを管理すると、見つけようと毛を裂くでしょう。 参照は本質的にC ++では安全ではありません

技術的にはこれは無効な参照であり、ヌル参照ではありません。 C ++では、他の言語で見られるように、null参照を概念としてサポートしていません。 他の種類の無効な参照もあります。 すべての無効な参照は、無効なポインタを使用するのと同じように、 未定義の動作の幽霊を発生させます。

実際のエラーは、参照への代入に先立って、NULLポインタの逆参照にあります。 しかし、私は、その条件でエラーを生成するコンパイラは認識していません。エラーはコード内の一点に伝播します。 それがこの問題をとても狡猾にしているのです。 たいていの場合、NULLポインタを参照解除すると、その場所でクラッシュし、それを把握するためのデバッグはあまり必要ありません。

上記の私の例は短く、人工的です。 より現実的な例があります。

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

私はnull参照を取得する唯一の方法は不正な形式のコードによるものであり、いったんそれを取得すると未定義の動作が得られていることを繰り返し言いたいと思います。 ヌル参照をチェックするのは決して意味がありません 。 たとえばif(&bar==NULL)...試すことができif(&bar==NULL)...しかし、コンパイラが文を最適化するかもしれません! 有効な参照はNULLになることはありませんので、コンパイラのビューでは常に比較はfalseで、 if節をデッドコードとして取り除くことは自由です。これは未定義の動作の本質です。

問題を避ける適切な方法は、参照を作成するNULLポインタの逆参照を避けることです。 これを実現する自動化された方法があります。

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

優れた文章力を持つ人からのこの問題の古い見方については、Jim HyslopとHerb SutterのNull参照を参照してください。

逆参照の危険の別の例として、Raymond Chenが別のプラットフォームにコードを移植しようとしているときに未定義の動作公開してください。

私は参照が構文的な砂糖であることを知っているので、コードは読みやすく、書くのが簡単です。

しかし、違いは何ですか?

以下の回答とリンクからの要約:

  1. バインディング後に参照を再割り当てすることはできませんが、ポインタは何度でも再割り当てできます。
  2. ポインターはどこでも指すことができません( NULL )が、参照は常にオブジェクトを参照します。
  3. できるだけポインタのように参照のアドレスを取ることはできません。
  4. "参照算術"はありません(ただし、参照によって指されたオブジェクトのアドレスを取得し、 &obj + 5ようにポインタ演算を行うことができます)。

誤解を明確にするために:

C ++標準では、コンパイラが参照をどのように実装するかを決めるのを非常に慎重にしていますが、すべてのC ++コンパイラは参照をポインタとして実装しています。 つまり、次のような宣言があります。

int &ri = i;

それが完全離れて最適化されていない場合 は、ポインタと同じ量のストレージを割り当て、 iのアドレスをそのストレージに配置します。

したがって、ポインタと参照の両方が同じ量のメモリを使用します。

原則として、

  • 有用で自己記述的なインターフェースを提供するために、関数パラメーターと戻り値の型で参照を使用してください。
  • アルゴリズムとデータ構造を実装するためのポインタを使用します。

面白い読書:


C ++リファレンスとは( Cプログラマー向け

参照定数ポインタ (定数値へのポインタと混同しないでください)と考えることができます。つまり、コンパイラは*演算子を自動的に適用します。

すべての参照をnull以外の値で初期化する必要があります。そうしないと、コンパイルは失敗します。 参照のアドレスを取得することはできません。代わりに、参照演算子は参照された値のアドレスを返します。また参照で算術演算を行うこともできません。

Cプログラマは、インダイレクションが発生したときや、関数シグネチャを見ずに引数が値渡しやポインタ渡しになったときに、もはや明白にならないので、C ++リファレンスを嫌うかもしれません。

C ++プログラマは、ポインタが安全でないとみなされるときにポインタを使用することを嫌うかもしれません。ただし、参照は実際には間違った場合を除いて定数ポインターよりも安全ではありません。

C ++ FAQから次の文を考えてみましょう:

参照は、基本となるアセンブリ言語のアドレスを使用して実装されることがよくありますが、オブジェクトへの面白い見栄えのポインタとして参照を考えないでください。 参照オブジェクトです。 これはオブジェクトへのポインタでもオブジェクトのコピーでもありません。 それ目的です。

しかし、リファレンスが実際にオブジェクトであった場合、どのように参照が絡まっている可能性がありますか? 管理されていない言語では、参照はポインタより安全なものではありません。一般に、スコープの境界を超えて値を確実に別名にする方法はありません。

なぜ私はC ++の参考文献が有用であると考える

Cのバックグラウンドから来たC ++リファレンスは、ややばかげたコンセプトのように見えるかもしれませんが、可能であればポインターの代わりにそれらを使うべきです:自動間接指定便利で、 RAIIを扱うときに参照が特に便利になります。利点ではなく、むしろ慣用的なコードを書くことをそれほど厄介ではないからです。

RAIIはC ++の中心的概念の1つですが、意味をコピーすることとはそれほど重要ではありません。 オブジェクトを参照渡しすると、コピーが含まれていないため、これらの問題は回避されます。 言語での参照が存在しない場合は、代わりにポインタを使用する必要があります。これは使用するのが面倒なので、ベストプラクティスの解決策が選択肢より簡単になるような言語設計の原則に違反します。


あなたが実際にどのようなスペースを取っても(コードを実行せずに)何らかの副作用を実際に見ることができないので、どのくらいのスペースがかかるかは問題ではありません。

一方、参照とポインタの主な違いの1つは、const参照が有効範囲外になるまで、const参照に割り当てられた一時変数が存続することです。

例えば:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

印刷されます:

in scope
scope_test done!

これは、ScopeGuardが動作するための言語メカニズムです。


ポインタと参照の間には、非常に重要な非技術的な違いがあります。ポインタによって関数に渡される引数は、非const参照によって関数に渡される引数よりもはるかに目に見えます。 例えば:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

C言語のように見える呼び出しfn(x)は、値渡ししかできないので、間違いなく修正することはできませんx。ポインタを渡す必要がある引数を変更しますfn(&x)。したがって、引数が前にない場合は、&変更されません。(逆に、ポインタで&大規模な読み取り専用構造体を渡さなければならない場合があるため、変更されたことは真実ではありませんでしたconst。)

これは、コードを読むときに便利な機能だと主張する人もいますがconst、たとえ関数が期待しない場合でもポインタパラメータは常に非参照ではなく変更可能なパラメータに使用されるべきnullptrです。つまり、これらの人々は、fn3()上記のような機能署名は許可されてはならないと主張する。GoogleのC ++スタイルのガイドラインはその一例です。


また、インライン化された関数へのパラメータである参照は、ポインタとは異なる方法で扱うことができます。

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

多くのコンパイラは、ポインタバージョン1をインライン展開すると実際にメモリへの書き込みを強制します(アドレスを明示的に取っています)。しかし、彼らはより最適なレジスタにリファレンスを残します。

もちろん、インライン化されていない関数の場合、ポインタと参照は同じコードを生成しますが、関数によって変更されて返されない場合は、参照よりも組み込み関数を渡す方が良いです。


参照の別の興味深い使用法は、ユーザー定義型のデフォルト引数を指定することです:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

デフォルトのフレーバは、 '一時的な参照へのバインド定数参照'を使用します。


参照はポインタと非常によく似ていますが、コンパイラの最適化に役立つように特別に作られています。

  • 参照は、どの参照エイリアスがどの変数をトレースするかをコンパイラが実質的に容易にするように設計されています。 2つの主要な機能が非常に重要です。「参照算術」はなく、参照の再割り当てはありません。 これにより、コンパイラはコンパイル時にどの変数にエイリアスがあるかを知ることができます。
  • 参照は、コンパイラがレジスタに格納するようなメモリアドレスを持たない変数を参照することができます。 ローカル変数のアドレスを取った場合、コンパイラがレジスタに格納することは非常に困難です。

例として:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

最適化コンパイラは、私たちが[0]と[1]のかなりの束にアクセスしていることを認識するかもしれません。 アルゴリズムを最適化して、

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

このような最適化を行うには、呼び出し中に配列[1]を変更することができないことを証明する必要があります。 これはむしろ簡単です。 私は決して2よりも小さくないので、array [i]は決してarray [1]を参照することはできません。 maybeModify()には参照としてa0が与えられます(別名配列[0])。 「参照」算術がないので、compilerはmaybeModifyがxのアドレスを決して得られないことを証明しなければならず、配列[1]を変更しないことが証明されています。

将来のコールがa0に一時的なレジスタコピーを持っている間に[0]を読み書きできる方法がないことも証明しなければなりません。 多くの場合、参照がクラスインスタンスのような永続的な構造に決して格納されないことが明らかであるため、これは証明するのは些細なことです。

今、ポインタで同じことをする

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

行動は同じです。 maybeModifyが配列[1]を変更していないことを証明するのはずっと難しくなりました。 猫は鞄から出ている。 今度は、はるかに難しい証明をする必要があります:maybeModifyの静的解析が、&x + 1に書き込まないことを証明することです。配列[0]を参照できるポインタを決して節約できないことも証明しなければなりません。トリッキーな

現代のコンパイラは静的解析でますます良くなってきていますが、それらを助けて参照を使用することは常に良いことです。

もちろん、そのような巧妙な最適化を除けば、コンパイラは必要に応じて参照を実際にポインタに変換します。

編集:この回答を投稿して5年後、同じアドレッシングの概念を見るのとはちょうど異なる方法とは異なる参照がある場合、実際の技術的な違いが見つかりました。 参照は、ポインタができない方法で一時的なオブジェクトの寿命を変更することができます。

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

通常、 createF(5)呼び出しによって作成されたものなどの一時的なオブジェクトは、式の最後に破棄されます。 しかし、そのオブジェクトを参照refにバインドすることで、C ++は、 refが有効範囲外になるまでその一時オブジェクトの存続期間を延長します。


実際、リファレンスは実際にポインタのようなものではありません。

コンパイラは変数に "参照"を保持し、名前をメモリアドレスに関連付けます。 コンパイル時に変数名をメモリアドレスに変換するのはその仕事です。

参照を作成するときは、ポインタ変数に別の名前を割り当てることだけをコンパイラーに指示します。 そのため、参照は「nullを指し示す」ことができません。なぜなら、変数はそうではなくてはいけないからです。

ポインタは変数です。 それらには他の変数のアドレスが含まれているか、またはnullにすることができます。 重要なことは、ポインタには値があり、参照にはそれが参照している変数しかないことです。

実際のコードの説明を次に示します。

int a = 0;
int& b = a;

ここではaを指す別の変数を作成していません。 aの値を保持するメモリコンテンツに別の名前を追加するだけです。 このメモリにはab a 2つの名前があり、どちらの名前でもアドレス指定が可能です。

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

関数を呼び出すとき、コンパイラは通常、引数をコピーするためのメモリ空間を生成します。 ファンクションシグネチャは、作成する必要があるスペースを定義し、これらのスペースに使用する名前を指定します。 パラメータを参照として宣言すると、メソッド呼び出し中に新しいメモリ空間を割り当てる代わりに、入力変数のメモリ空間を使用するようにコンパイラに指示するだけです。 あなたの関数が呼び出し元のスコープで宣言された変数を直接操作すると言うのは、奇妙に思えるかもしれませんが、コンパイルされたコードを実行するときはスコープがなくなります。 単純なフラットなメモリがあり、関数コードは任意の変数を操作できます。

コンパイル時に、extern変数を使用する場合のように、コンパイラーが参照を認識できない場合があります。 そのため、リファレンスは、基礎となるコードのポインタとして実装されても実装されなくてもよい。 しかし、私があなたに与えた例では、ポインタで実装される可能性はほとんどありません。


ポインタと参照の違い

ポインタは0に初期化され、参照は初期化されません。実際には、参照はオブジェクトを参照する必要がありますが、ポインタはnullポインタになる可能性があります。

int* p = 0;

しかし、私たちは持っていることができませんしint& p = 0;、またint& p=5 ;

実際に正しく行うには、最初にオブジェクトを宣言して定義しておかなければなりません。次に、そのオブジェクトへの参照を行うことができるため、前のコードの正しい実装は次のようになります。

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

もう1つの重要な点は、初期化なしでポインタの宣言を行うことができるということですが、参照の場合には常にそのようなことを行うことはできません。しかし、このようなポインタの使用は危険であるため、一般に、ポインタが実際に何かを指しているかどうかを確認します。参照の場合、そのようなチェックは必要ではありません。宣言中にオブジェクトへの参照が必須であることが既に分かっているためです。

別の違いは、ポインタが別のオブジェクトを指すことができるということですが、参照は常に同じオブジェクトを参照しています。この例を考えてみましょう:

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

もう一つのポイント:STLテンプレートのようなテンプレートがある場合、そのような種類のクラステンプレートは、ポインタではなく参照を返して、演算子[]を使用して簡単に読み込みまたは新しい値を割り当てることができます。

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="

参照は、いくつかのメモリに与えられた別の名前ではありません。これは、使用時に自動的に参照解除される不変のポインタです。基本的には、

int& j = i;

内部的には

int* const j = &i;

たぶんいくつかの隠喩が助けになるでしょう。デスクトップのスクリーンスペースのコンテキストでは、

  • リファレンスでは、実際のウィンドウを指定する必要があります。
  • ポインターには、画面上のスペースの位置が必要です。画面には、そのウィンドウタイプが0個以上含まれていることが保証されています。

私はこれらのいずれかが必要でない限り、参考文献を使用します:

  • ヌルポインターは、センチネル値として使用できます。関数のオーバーロードやブールの使用を避けるために、しばしば安価な方法です。

  • ポインタで算術演算を行うことができます。例えば、p += offset;


C ++ではポインタへの参照が可能ですが、逆のことはできません。参照へのポインタが使用できないことを意味します。ポインタへの参照は、ポインタを変更するためのよりクリーンな構文を提供します。この例を見てください:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

そして、上記のプログラムのCバージョンを考えてみましょう。Cでは、ポインタへのポインタ(複数の間接)を使用する必要があり、混乱の原因となり、プログラムが複雑に見えます。

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

ポインタへの参照の詳細については、以下を参照してください。

私が言ったように、参照へのポインタは不可能です。以下のプログラムを試してみてください:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}

違いは、非定数ポインタ変数(定数へのポインタと混同しないでください)は、プログラム実行中のある時点で変更される可能性があり、ポインタのセマンティクス(&、*)演算子が必要であり、参照は初期化(コンストラクタのイニシャライザリストでのみ設定できますが、それ以外の場合は設定できません)、通常の値にアクセスするセマンティクスを使用します。基本的には、私が非常に古い本を読んだときにオペレータのオーバーロードをサポートするためのリファレンスが導入されました。誰かがこのスレッドで述べているように、ポインタは0か任意の値に設定できます。 0(NULL、nullptr)は、ポインタが何も初期化されていないことを意味します。 nullポインタを逆参照するのはエラーです。しかし実際には、ポインタには正しいメモリ位置を指し示さない値が含まれていることがあります。順番に参照は、あなたが常にそれに正しい型のrvalueを提供するという事実のために参照することができない何かへの参照を初期化することをユーザーに許可しないようにしてください。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、詳細を詳しく調べない方がよいでしょう。マシンレベルでは、ポインタと参照の両方がポインタを介して一様に動作します。本質的な参照が構文的な砂糖であるとしましょう。右辺値参照はこれとは異なります。これらは自然にスタック/ヒープオブジェクトです。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、詳細を詳しく調べない方がよいでしょう。マシンレベルでは、ポインタと参照の両方がポインタを介して一様に動作します。本質的な参照が構文的な砂糖であるとしましょう。右辺値参照はこれとは異なります。これらは自然にスタック/ヒープオブジェクトです。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、詳細を詳しく調べない方がよいでしょう。マシンレベルでは、ポインタと参照の両方がポインタを介して一様に動作します。本質的な参照が構文的な砂糖であるとしましょう。右辺値参照はこれとは異なります。これらは自然にスタック/ヒープオブジェクトです。


あなたが抽象的または学術的な方法でコンピュータ言語を勉強するのに精通していない場合、意味の違いがあります。

最も高いレベルでは、参照のアイデアは透明な「別名」であるということです。あなたのコンピュータは、それらを動作させるためにアドレスを使用するかもしれませんが、あなたはそれを心配してはいけません。あなたはそれらを既存のオブジェクトの "別の名前"と考えるべきであり、その構文はそれを反映しています。ポインタよりも厳密なので、ダングリングポインタを作成しようとしているときよりも、ダングリングリファレンスを作成しようとしているときにコンパイラがより確実に警告を出すことができます。

それ以外にも、ポインタと参照の間には実用的な違いがあります。それらを使用するための構文は明らかに異なります。また、参照を「再配置」することも、何も参照しないことも、参照へのポインタを持たせることもできません。


  1. ポインタを再割り当てすることができます:

    int x = 5;
    int y = 6;
    int *p;
    p =  &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);
    

    参照はできません。初期化時に参照を割り当てる必要があります。

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. ポインタはスタック上に独自のメモリアドレスとサイズ(x86では4バイト)を持ちますが、参照は同じメモリアドレス(元の変数)を共有しますが、スタック上のスペースも占有します。 参照は元の変数自体と同じアドレスを持つので、同じ変数の別の名前として参照を考えることは安全です。 注:ポインタが指しているものは、スタックまたはヒープ上にある可能性があります。 参考にしてください。 このステートメントで私の主張は、ポインタがスタックを指していなければならないということではありません。 ポインタはメモリアドレスを保持する単なる変数です。 この変数はスタック上にあります。 参照にはスタック上に独自の領域があり、アドレスは参照する変数と同じであるためです。 スタックとヒープの詳細 これは、コンパイラがあなたに言わないリファレンスの実際のアドレスがあることを意味します。

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. 余分なレベルのインダイレクションを提供するポインタへのポインタを持つことができます。 参照は1レベルの間接参照のみを提供します。

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. ポインタはnullptr直接割り当てることができますが、参照はできません。 十分な努力をして、どのように知っていれば、参照nullptrアドレスを作ることができます。 同様に、十分に試してみると、ポインタへの参照を持つことができ、その参照にはnullptrを含めることができます。

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
    
  5. ポインタは配列を反復することができます。ポインタを指している次の項目に進むには++を使い、5番目の要素に行くには+ 4を使い++ 。 これは、ポインタが指すオブジェクトの大きさに関係なくです。

  6. 参照は直接使用できるのに対して、ポインターは参照先のメモリー位置にアクセスするには*で逆参照する必要があります。 クラス/構造体へのポインタは、そのメンバにアクセスするために->を使用しますが、参照は.

  7. ポインタは、メモリアドレスを保持する変数です。 参照がどのように実装されているかにかかわらず、参照は参照する項目と同じメモリアドレスを持ちます。

  8. 参照は配列に埋め込むことはできませんが、ポインタは(user @litbによって記述されます)

  9. Const参照は一時オブジェクトにバインドできます。 ポインタはできません(間接指定なしではできません)。

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.
    

    これにより、引数リストなどでconst&安全に使用できます。


混乱を招く恐れがあるので、私はいくつかのインプットを投げたいと思いますが、コンパイラがどのように参照を実装するかに依存していると確信していますが、gccの場合、参照はスタック上の変数実際には正しいとは言えませんが、これを例に取ってください:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

これは、これを出力します:

THIS IS A STRING
0xbb2070 : 0xbb2070

気付いた場合でも、メモリアドレスはまったく同じです。つまり、リファレンスがヒープ上の変数を指していることを意味します。今、あなたが本当に気まずい人になりたいのであれば、これもうまくいきます:

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

これは、これを出力します:

THIS IS A STRING

したがって、リファレンスはフードの下のポインタであり、どちらもメモリアドレスを格納しています。アドレスが指すアドレスは無関係です。std :: cout << str_ref;を呼び出すとどうなりますか?削除&str_refを呼び出した後に?まあ、明らかにコンパイルはうまくいきますが、実行時にセグメンテーション違反が発生します。なぜなら、もはや有効な変数を指していないからです。スコープから外れるまではまだ存在する壊れた参照がありますが、無駄です。

言い換えれば、参照はポインタメカニックスを抽象化したポインタに過ぎず、安全で使いやすくなっています(ポインタの数学的なミスはなく、 '。'や ' - ' 'などのミックスアップもありません)。上記の私の例のようなナンセンスはしないでください;)

今すぐにかかわらず、コンパイラが参照をどのように扱うかの、それはなります常に参照するので、フードの下ポインタのいくつかの種類を持っている必要があります期待通りに動作するために特定のメモリアドレスに特定の変数を参照してください、何ので、(この歩き回るはありません用語「参照」)。

参照で覚えておくべき重要な唯一の主要なルールは、宣言時に定義する必要があることです(ヘッダー内の参照は例外です。その場合は、コンストラクターで定義する必要があります。それを定義するには遅すぎる)。

覚えておいてください。上記の私の例はちょうどその例です。参照が何であるかを示す例ですが、それらの方法で参照を使用することは決してありません!リファレンスを適切に使用するためには、ここで頭の上の釘を打つ答えがたくさんあります


あなたは最も重要な部分を忘れてしまった:

ポインタによるメンバアクセス->
参照を使用したメンバーアクセス.

foo.barは、 viEmacsよりも明らかに優れているのと同じ方法でfoo->barよりも明らかに優れていfoo->bar :-)


構文的な砂糖とは別に、参照はconstポインタです( constポインタではありません )。 参照変数を宣言するときにその参照先を確定し、後でそれを変更することはできません。

更新:もう少し考えてみると、重要な違いがあります。

constポインタのターゲットは、アドレスを取得しconstキャストを使用することで置き換えることができます。

参照のターゲットは、UBのいずれの方法でも置き換えることはできません。

これにより、コンパイラは参照に対してより多くの最適化を行うことができます。


私はいつもthisルールによってC ++のコア・ガイドラインから決める:

"引数なし"が有効なオプションである場合、T *よりT *を優先する


参照は別の変数の別名ですが、ポインタは変数のメモリアドレスを保持します。参照は、一般に、渡されたオブジェクトがコピーではなくオブジェクト自体であるように、関数パラメータとして使用されます。

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 

あなたが本当に賢い人になりたいのであれば、ポインタではできない参照を使って行うことができることがあります:一時的なオブジェクトの存続期間を延長すること。 C ++では、const参照を一時オブジェクトにバインドすると、そのオブジェクトの存続期間が参照の存続期間になります。

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

この例では、s3_copyは連結の結果である一時オブジェクトをコピーします。 本質的にs3_referenceは一時的なオブジェクトになります。 これは実際に参照と同じ存続期間を持つ一時オブジェクトへの参照です。

constなしでこれを試してみると、コンパイルに失敗するはずです。 非const参照を一時オブジェクトにバインドすることはできませんし、その問題に関してはアドレスを取得することもできません。


もう1つの違いは、void型へのポインタを持つことができます(そして何かへのポインタを意味します)が、voidへの参照は禁止されています。

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

私はこの特定の違いに本当に満足しているとは言えません。私は、住所を持つものには意味の参照を許可し、それ以外は参照のために同じ動作を許すことを望むでしょう。参照を使用してmemcpyのようなCライブラリ関数のいくつかの同等物を定義することができます。


両方の参照とポインタは間接的に別の値にアクセスするために使用されますが、参照とポインタの間には2つの重要な違いがあります。1つは、参照が常にオブジェクトを参照することです。初期化せずに参照を定義するのはエラーです。代入の振る舞いは、2番目の重要な相違点です。参照を代入すると、参照がバインドされているオブジェクトが変更されます。別のオブジェクトへの参照を再バインドしません。初期化されると、参照は常に同じ基本オブジェクトを参照します。

これら2つのプログラムの断片を考えてみましょう。最初に、別のポインタに1つのポインタを割り当てます。

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

代入、ivalの後、piで指定されたオブジェクトは変更されません。代入によってpiの値が変更され、別のオブジェクトをポイントします。次に、2つの参照を割り当てる同様のプログラムを考えてみましょう。

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

この割り当ては、riによって参照される値であり、参照自体ではなく変更されます。割り当て後、2つの参照は元のオブジェクトを参照しており、それらのオブジェクトの値も同じようになりました。





c++-faq