web-dev-qa-db-ja.com

参照がC ++で再シールできないのはなぜですか

C++参照には2つのプロパティがあります。

  • それらは常に同じオブジェクトを指します。
  • それらは0であってはなりません。

ポインターは反対です:

  • それらは異なるオブジェクトを指すことができます。
  • それらは0にすることができます。

C++に「null不可、再シーズ可能な参照またはポインタ」がないのはなぜですか?参照を再シールできないようにするべきであるという正当な理由は考えられません。

編集:「関連付け」(ここでは「参照」または「ポインタ」という単語は避けています)が無効にならないようにするために、通常は参照を使用するため、質問が頻繁に出てきます。

「このrefが常に同じオブジェクトを参照するのは素晴らしい」と思ったことはないと思います。参照が再シール可能である場合でも、現在の動作は次のようになります。

int i = 3;
int& const j = i;

これはすでに正当なC++ですが、意味がありません。

私はこのように私の質問を言い直します:「「参照isオブジェクト」デザインの背後にある理論的根拠は何でしたか?なぜ参照を常ににすると、constとして宣言されたときだけでなく、同じオブジェクトになると便利だと考えられました。 "

乾杯、フェリックス

67
TheFogger

C++では、「参照isオブジェクト」とよく言われます。ある意味では本当です。参照はソースコードのコンパイル時にポインタとして処理されますが、参照は、関数が呼び出されたときにコピーされないオブジェクトを示すことを目的としています。参照は直接アドレス指定できないため(たとえば、参照にアドレスがなく、オブジェクトのアドレスを返すなど)、それらを再割り当てしても意味的に意味がありません。さらに、C++にはすでにポインタがあり、再設定のセマンティクスを処理します。

32
rlbond

そのため、0にすることのできない再密閉可能なタイプはありません。そうでない場合は、3つのタイプの参照/ポインターを含めました。どれもわずかな利益のために言語を複雑にするだけです(そして、4番目の型も追加しないのはなぜですか。

よりよい質問は、リファレンスを再シールできるようにしたいのはなぜですか?もしそうなら、それはそれらを多くの状況であまり役に立たなくするでしょう。コンパイラがエイリアス分析を行うのが難しくなります。

JavaまたはC#での参照が再シール可能である主な理由は、ポインターの機能を実行するためです。オブジェクトを指します。オブジェクトのエイリアスではありません。

次の影響は何ですか?

int i = 42;
int& j = i;
j = 43;

今日のC++では、再格納できない参照があるため、それは簡単です。 jはiのエイリアスで、iは値43になります。

参照が再シール可能であった場合、3行目は参照jを別の値にバインドします。それはもはやiをエイリアスしませんが、代わりに整数リテラル43(もちろん無効です)になります。または、おそらくより単純な(または少なくとも構文的に有効な)例:

int i = 42;
int k = 43;
int& j = i;
j = k;

再密閉可能な参照付き。このコードを評価した後、jはkを指します。 C++の再装着できない参照では、jは引き続きiを指し、iには値43が割り当てられます。

再参照可能な参照を作成すると、言語のセマンティクスが変更されます。参照を別の変数のエイリアスにすることはできません。代わりに、独自の代入演算子を持つ別のタイプの値になります。そして、参照の最も一般的な使用法の1つは不可能です。そして、それと引き換えに何も得られないでしょう。参照用に新しく得られた機能は、すでにポインタの形で存在していました。したがって、同じことを行うには2つの方法があり、現在のC++言語の参照が行うことを行う方法はありません。

18
jalf

再密閉可能な参照は、機能的にはポインタと同じです。

Null可能性について:このような「再設定可能な参照」がコンパイル時にNULLでないことは保証できないため、そのようなテストは実行時に行う必要があります。これは、初期化またはNULLの割り当て時に例外をスローするスマートポインタスタイルのクラステンプレートを作成することで、自分で実現できます。

struct null_pointer_exception { ... };

template<typename T>
struct non_null_pointer {
    // No default ctor as it could only sensibly produce a NULL pointer
    non_null_pointer(T* p) : _p(p) { die_if_null(); }
    non_null_pointer(non_null_pointer const& nnp) : _p(nnp._p) {}
    non_null_pointer& operator=(T* p) { _p = p; die_if_null(); }
    non_null_pointer& operator=(non_null_pointer const& nnp) { _p = nnp._p; }

    T& operator*() { return *_p; }
    T const& operator*() const { return *_p; }
    T* operator->() { return _p; }

    // Allow implicit conversion to T* for convenience
    operator T*() const { return _p; }

    // You also need to implement operators for +, -, +=, -=, ++, --

private:
    T* _p;
    void die_if_null() const {
        if (!_p) { throw null_pointer_exception(); }
    }
};

これはときどき役立つかもしれません-non_null_pointer<int>パラメータを取る関数は、確かにint*を取る関数よりも多くの情報を呼び出し元に伝えます。

5
j_random_hacker

参照はポインタではなく、バックグラウンドでポインタとして実装される場合がありますが、そのコアコンセプトはポインタと同等ではありません。参照はそれと同じように見るべきです*is*参照しているオブジェクト。したがって、これを変更したり、NULLにすることはできません。

ポインタは、メモリアドレスを保持する変数です。 ポインタ自体には独自のメモリアドレスがあり、そのメモリアドレス内には、ポインタが指していると言われる別のメモリアドレスが保持されています。 参照は同じではなく、独自のアドレスを持たないため、別のアドレスを「保持」するように変更することはできません。

パラシフトC++ FAQ参考文献 がそれを最もよく表していると思います:

重要な注意:参照は、基になるアセンブリ言語のアドレスを使用して実装されることがよくありますが、参照をオブジェクトへの奇妙に見えるポインタと考えないでください。参照はオブジェクトです。これは、オブジェクトへのポインタでも、オブジェクトのコピーでもありません。それが対象です。

そして再び FAQ 8.5

ポインターとは異なり、参照がオブジェクトにバインドされると、別のオブジェクトに「再配置」することはできません。参照自体はオブジェクトではありません(IDはありません。参照のアドレスを取得すると、参照先のアドレスがわかります。覚えておいてください:参照はその参照先です)。

5
Brian R. Bondy

C++の参照に「エイリアス」と名前を付けるのは、おそらく混乱が少ないでしょう。他の人が述べたように、C++での参照は なので 変数 彼らはポインタとしてではなく、参照 変数に。そのため、私は彼らが正当な理由を考えることはできません すべき リセット可能です。

ポインタを扱う場合、nullを値として許可することはしばしば意味があります(そうでなければ、代わりに参照が必要になるでしょう)。特にnullの保持を許可しない場合は、常に独自のスマートポインター型をコーディングできます;)

4
snemarch

興味深いことに、ここでの多くの回答は少しあいまいであるか、ポイントの横にさえあります(たとえば、参照をゼロまたは類似にすることができないためではありません。実際、参照がゼロである例を簡単に作成できます)。

参照の再設定が不可能である本当の理由はかなり単純です。

  • ポインターを使用すると、次の2つのことが可能になります。ポインターの後ろの値を変更する(-> または *演算子)、およびポインタ自体を変更します(直接代入=)。例:

    int a;
    int * p = &a;
    1. 値を変更するには逆参照が必要です:*p = 42;
    2. ポインタの変更:p = 0;
  • 参照を使用すると、値のみを変更できます。どうして?リセットを表現する構文は他にないので。例:

    int a = 10;
    int b = 20;
    int & r = a;
    r = b; // re-set r to b, or set a to 20?

つまり、参照を再設定することが許可されている場合、あいまいになります。参照渡しの場合はさらに意味があります。

void foo(int & r)
{
    int b = 20;
    r = b; // re-set r to a? or set a to 20?
}
void main()
{
    int a = 10;
    foo(a);
}

それが役に立てば幸い:-)

3
dhaumann

C++参照はときどき強制されることがあります0一部のコンパイラでは(それはそうするのは悪い考えです*、そしてそれは標準に違反しています*)。

int &x = *((int*)0); // Illegal but some compilers accept it

編集:自分よりも標準をよく知っているさまざまな人々によると、上記のコードは「未定義の動作」を生成します。 GCCとVisual Studioの少なくとも一部のバージョンでは、これが期待どおりの動作をするのを見てきました。つまり、ポインターをNULLに設定するのと同じです(アクセスするとNULLポインター例外が発生します)。

2
Mr Fooz

あなたはこれを行うことができません:

int theInt = 0;
int& refToTheInt = theInt;

int otherInt = 42;
refToTheInt = otherInt;

... secondIntとfirstIntが同じ値を持たないのと同じ理由で、ここでは:

int firstInt = 1;
int secondInt = 2;
secondInt = firstInt;
firstInt = 3;

assert( firstInt != secondInt );
1
John Dibling

これは実際には答えではありませんが、この制限の回避策です。

基本的に、参照を「再バインド」しようとすると、実際には同じ名前を使用して次のコンテキストで新しい値を参照しようとしています。 C++では、ブロックスコープを導入することでこれを実現できます。

ジャルフの例では

int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
j = k;

iを変更する場合は、上記のように記述します。ただし、jの意味をkに変更する場合は、次のようにします。

int i = 42;
int k = 43;
int& j = i;
//change i, or change j?
//change j!
{
    int& j = k;
    //do what ever with j's new meaning
}
1
Earth Engine

C++での参照がnull可能ではないという事実は、それらが単なるエイリアスであることの副作用です。

0
hasen

彼らはなぜこれのために参照代入演算子(たとえば:=)を作成しなかったのかといつも疑問に思いました。

誰かの神経質になるために、構造体の参照のターゲットを変更するためのコードを書きました。

いいえ、トリックを繰り返すことはお勧めしません。十分に異なるアーキテクチャに移植すると壊れます。

0
Joshua

参照であるメンバー変数が必要で、それを再バインドできるようにしたい場合は、回避策があります。私はそれが有用で信頼できると思いますが、それはメモリレイアウトに関するいくつかの(非常に弱い)仮定を使用していることに注意してください。それがコーディング標準の範囲内であるかどうかを決めるのはあなた次第です。

#include <iostream>

struct Field_a_t
{
    int& a_;
    Field_a_t(int& a)
        : a_(a) {}
    Field_a_t& operator=(int& a)
    {
        // a_.~int(); // do this if you have a non-trivial destructor
        new(this)Field_a_t(a);
    }
};

struct MyType : Field_a_t
{
    char c_;
    MyType(int& a, char c)
        : Field_a_t(a)
        , c_(c) {}
};

int main()
{
    int i = 1;
    int j = 2;
    MyType x(i, 'x');
    std::cout << x.a_;
    x.a_ = 3;
    std::cout << i;
    ((Field_a_t&)x) = j;
    std::cout << x.a_;
    x.a_ = 4;
    std::cout << j;
}

再割り当て可能な参照フィールドごとに個別の型が必要であり、それらを基本クラスにするため、これはあまり効率的ではありません。また、単一の参照型を持つクラスには、MyTypeのランタイムバインディングを破壊する可能性のある__vfptrまたはその他のtype_id関連のフィールドがないことを前提としています。私が知っているすべてのコンパイラーはその条件を満たしています(そうしないことはほとんど意味がありません)。

0
lorro

受け入れた回答に同意します。しかし、一貫性を保つために、それらはポインタとほとんど同じように動作します。

struct A{
    int y;
    int& x;
     A():y(0),x(y){}
};

int main(){
  A a;
  const A& ar=a;
  ar.x++;
}

動作します。見る

const参照によって渡されるクラスの参照メンバーの動作に関する設計上の理由

0

半分真剣であること:ポインタと少し異なるようにするためのIMHO;)あなたは次のように書くことができます:

MyClass & c = *new MyClass();

後で書くこともできれば:

c = *new MyClass("other")

ポインタと一緒に参照があることは理にかなっていますか?

MyClass * a =  new MyClass();
MyClass & b = *new MyClass();
a =  new MyClass("other");
b = *new MyClass("another");
0
anon

それは最適化に関係していると思います。

静的最適化は、変数のメモリのビットが何であるかを明確に知ることができる場合、muchより簡単です。ポインタはこの状態を壊し、再設定可能な参照もそうします。

0
dmckee

時々、物事は再指摘可能であってはならないからです。 (例えば、シングルトンへの参照。)

引数がnullになり得ないことを知っていることは、関数にとって素晴らしいことだからです。

しかし、ほとんどの場合、実際にはポインタであるものを使用できますが、ローカル値オブジェクトのように機能します。 C++は、Stroustrupを引用して、クラスインスタンスを「intとして実行」するように懸命に試みます。 intはマシンレジスタに収まるため、vaueによるintの受け渡しは安価です。多くの場合、クラスはintよりも大きく、値で渡すとかなりのオーバーヘッドが発生します。

値オブジェクトのように見えるポインター(多くの場合、intまたは2つのintのサイズ)を渡すことができるため、間接参照の「実装の詳細」なしで、よりクリーンなコードを記述できます。また、演算子のオーバーロードと共に、intで使用される構文と同様の構文を使用するクラスを記述できます。特に、intなどのプリミティブやクラス(複素数クラスなど)に等しく適用できる構文を使用して、テンプレートクラスを記述できます。

そして、特にオペレーターのオーバーロードでは、オブジェクトを返す必要がある場所がありますが、ここでもポインターを返す方がはるかに安価です。もう一度、参照を返すのが「out」です。

そして、ポインタは難しいです。あなたのためではないかもしれませんし、ポインタがメモリアドレスの値にすぎないことに気づく人もいません。しかし、私のCS 101クラスを思い出して、彼らは多くの学生をつまずかせました。

char* p = s; *p = *s; *p++ = *s++; i = ++*p;

混乱する可能性があります。

ヘック、Cの40年後、ポインタ宣言が次のようになるべきかどうかにさえ人々はまだ同意できません。

char* p;

または

char *p;
0
tpdi