非const参照が一時オブジェクトにバインドできないのはなぜですか?


226

一時オブジェクトへの非const参照の取得が許可されないのはなぜgetx()ですか、どの関数が返しますか?明らかに、これはC ++標準では禁止されていますが、標準への参照はなく、そのような制限の目的に興味があります。

struct X
{
    X& ref() { return *this; }
};

X getx() { return X();}

void g(X & x) {}    

int f()
{
    const X& x = getx(); // OK
    X& x = getx(); // error
    X& x = getx().ref(); // OK
    g(getx()); //error
    g(getx().ref()); //OK
    return 0;
}
  1. C ++標準ではオブジェクトへの常時参照が禁止されていないため、オブジェクトの存続期間が原因ではないことは明らかです。
  2. 非定数関数の呼び出しが許可されているため、上記のサンプルでは一時オブジェクトが定数ではないことは明らかです。たとえばref()、一時オブジェクトを変更できます。
  3. さらにref()、コンパイラーをだますことができ、この一時オブジェクトへのリンクを取得できます。これにより問題が解決されます。

加えて:

彼らは、「const参照に一時オブジェクトを割り当てると、このオブジェクトの寿命が延びる」と述べ、「非const参照については何も言われていない」と述べています。私の追加の質問。次の割り当ては一時オブジェクトの寿命を延ばしますか?

X& x = getx().ref(); // OK

4
「オブジェクトの存続期間が原因ではありません」という部分には同意しません。これは、標準で一時オブジェクトをconst参照に割り当てると、このオブジェクトの存続期間がconst参照の存続期間まで延長されるということです。非constの参照については何も言われていません...
SadSido

3
さて、その原因は「非const参照については何も言われていない...」です。それは私の質問の一部です。これには意味がありますか?Standardの作者は、非constの参照について忘れていたかもしれませんが、次のコアの問題がすぐにわかりますか?
Alexey Malistov


4
@Michael:VCは右辺値を非const参照にバインドします。彼らはこれを機能と呼んでいますが、実際にはバグです。(それは本質的に非論理的であるため、バグではありませんが、愚かなエラーを防ぐために明示的に除外されているためです。)
sbi

回答:


97

右辺値参照に関するこのVisual C ++ブログ記事から

... C ++は誤って一時を変更することを望まないが、変更可能な右辺値で非constメンバー関数を直接呼び出すことは明示的であるため、許可される...

基本的に、一時オブジェクトであり、すぐに死ぬという理由で一時オブジェクトを変更しようとするべきではありません。非constメソッドの呼び出しが許可される理由は、まあ、あなたが何をしているかを理解していて、それについて明示的である(たとえばreinterpret_castを使用する)限り、いくつかの「ばかげた」ことをするのは大歓迎です。ただし、テンポラリを非const参照にバインドすると、オブジェクトの操作が消えるようにするために、それを「永久に」渡し続けることができます。これは、途中でこれを完全に忘れてしまったためです。

もし私があなただったら、私の機能のデザインを考え直します。g()が参照を受け入れるのはなぜですか?パラメータを変更しますか?いいえの場合、それをconst参照にします。そうであれば、なぜそれに一時を渡そうとしますか、それが変更中の一時であることを気にしませんか?とにかくgetx()が一時的に戻るのはなぜですか?実際のシナリオと達成しようとしていることを私たちと共有すると、それを行う方法についていくつかの良い提案が得られる場合があります。

言語に逆らってコンパイラをだまして問題を解決することはめったにありません-通常それは問題を引き起こします。


編集:コメントでの質問への対応:1)X& x = getx().ref(); // OK when will x die?-わからない、私は気にしません。これはまさに「言語に逆らう」という意味です。言語は「テンポラリーは、const参照にバインドされていない限り、ステートメントの最後で死ぬ。その場合、参照がスコープから外れると死ぬ」と述べています。その規則を適用すると、xはconst参照にバインドされていないため(コンパイラーはref()が返すものを認識していない)、次のステートメントの先頭ですでにデッドになっているようです。ただし、これは推測です。

2)目的を明確に述べた:一時的なものを変更することはできません。なぜなら、それが意味をなさないからです(C ++ 0x右辺値参照を無視する)。「では、なぜ非constメンバーを呼び出すことを許可されるのですか?」という質問。良い質問ですが、上で述べた質問よりも良い答えはありません。

3)まあ、私がxについてX& x = getx().ref();声明の最後で死ぬのが正しいなら、問題は明白です。

とにかく、あなたの質問とコメントに基づいて、私はこれらの余分な答えがあなたを満足させるとは思いません。これが最後の試み/要約です。C++委員会は、一時を変更することは意味がないと判断したため、非const参照へのバインドを許可しませんでした。コンパイラの実装か、歴史的な問題も関係していたのかもしれませんが、わかりません。その後、いくつかの特定のケースが発生し、すべてのオッズに対して、非constメソッドを呼び出すことで直接変更できるようにすることが決定されました。ただし、これは例外です。通常、一時を変更することはできません。はい、C ++はしばしば奇妙です。


2
@sbk:1)実際には、正しい表現は、「... 完全な式の終わりに...」です。「完全な表現」は、他の表現の部分表現ではないものとして定義されていると思います。これが常に「声明の終わり」と同じであるかどうかはわかりません。
sbi 2009年

5
@sbk:2)実際には、あなたがしている右辺値(一時)を変更することができました。組み込み型(intなど)では禁止されていますが、ユーザー定義型では許可されています:(std::string("A")+"B").append("C")
sbi 2009年

6
@sbk:3)Stroustrupが非定数参照への右辺値のバインドを禁止する理由を(D&Eで)与える理由は、Alexey g()がオブジェクトを変更する場合(非const参照を受け取る関数から期待される)、死ぬオブジェクトを変更するため、変更された値に誰も到達できません。これはおそらく誤りであると彼は言う。
sbi 2009年

2
@sbk:気分を害したとしたら申し訳ありませんが、2)がつまらないとは思いません。右辺値は、組み込みでない限り、変更しない限り、単にconstにはなりません。文字列の例など、私が何か悪いことをしているのか(JFTR:していません)を理解するのにしばらく時間がかかったので、この区別を真剣に受け止める傾向があります。
sbi 2009年

5
私はsbiに同意します-この問題はまったくつまらないことではありません。クラス型の右辺値が非定数に保たれるのが最も良いのは、移動セマンティクスのすべての基礎です。
Johannes Schaub-litb

38

コードでgetx()は、一時的なオブジェクト、いわゆる「右辺値」を返します。右辺値をオブジェクト(別名:変数)にコピーするか、それらをconst参照にバインドできます(これにより、参照の寿命が尽きるまで寿命が延長されます)。右辺値を非const参照にバインドすることはできません。

これは、ユーザーが式の最後で終了するオブジェクトを誤って変更しないようにするための、意図的な設計の決定でした。

g(getx()); // g() would modify an object without anyone being able to observe

これを行う場合は、最初にローカルコピーまたはオブジェクトのコピーを作成するか、それをconst参照にバインドする必要があります。

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

次のC ++標準には右辺値参照が含まれることに注意してください。したがって、参照として知っているものは、「左辺値参照」と呼ばれるようになっています。右辺値を右辺値参照にバインドすることが許可され、「rvalue-ness」で関数をオーバーロードできます。

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

右辺値参照の背後にある考え方は、これらのオブジェクトはとにかく死ぬので、その知識を利用して、「移動セマンティクス」と呼ばれるもの、特定の種類の最適化を実装できるということです。

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty

2
こんにちは、これは素晴らしい答えです。1つ知っておく必要があるのg(getx())は、署名が一時オブジェクトであり、一時オブジェクトg(X& x)get(x)返すため、が機能しないため、一時オブジェクト(rvalue)を非定数参照にバインドできないことです。そして、あなたの最初のコード部分で、私はそれがなると思うconst X& x2 = getx();の代わりにconst X& x1 = getx();...
SexyBeast

1
このバグを書いてから5年後に私の回答でこのバグを指摘していただきありがとうございます。:-/はい、あなたの推理が後方ビットいえ、正しいです:私たちがすることはできませんバインド以外の一時const(左辺値)の参照、およびによって返されたため、一時的なgetx()(とないget(x))への引数の左辺値参照にバインドすることはできませんg()
sbi 2014年

うーん、あなたは何を意味したgetx()(とありませんかget(x)
SexyBeast 2014年

私が書くとき、「...のgetXを()(及び(X)を取得しない)...」、私は、関数の名前があることを意味しgetx()、そしてませんget(x)(あなたが書いたように)。
sbi 2014年

この答えは、用語を混同しています。右辺値は式のカテゴリです。一時オブジェクトはオブジェクトです。右辺値は一時オブジェクトを表す場合とそうでない場合があります。一時オブジェクトは、右辺値で示される場合とされない場合があります。
MM

16

表示されているのは、演算子の連鎖が許可されていることです。

 X& x = getx().ref(); // OK

式は 'getx()。ref();'です。そして、これは「x」に割り当てる前に完了するまで実行されます。

getx()は参照を返さず、完全に形成されたオブジェクトをローカルコンテキストに返すことに注意してください。オブジェクトは一時的ですがconst ではないため、他のメソッドを呼び出して値を計算したり、他の副作用を発生させたりすることができます。

// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);

// or more commonly
std::cout << getManiplator() << 5;

このより良い例については、この回答の終わりを見てください

テンポラリを参照にバインドすることはできません。そうすると、式の最後で破棄されるオブジェクトへの参照が生成され、ぶら下がった参照が残ります(これは整然としているため、標準では整然としています)。

ref()によって返される値は有効な参照ですが、メソッドは、返されるオブジェクトの寿命に注意を払いません(そのコンテキスト内にその情報を持つことができないため)。あなたは基本的に次の同等のことをしました:

x& = const_cast<x&>(getX());

一時オブジェクトへのconst参照を使用してこれを実行しても問題ないのは、標準が一時オブジェクトの寿命を参照の寿命まで延長するため、一時オブジェクトの寿命がステートメントの終わりを超えて延長されるためです。

したがって、残っている唯一の質問は、なぜ標準が一時の参照を許可して、ステートメントの終わりを超えてオブジェクトの寿命を延ばしたくないのかということです。

これを行うと、コンパイラーが一時オブジェクトを正しく取得することが非常に難しくなるためです。これは一時的なものへのconst参照のために行われました。これは使用が制限されているため、オブジェクトのコピーを作成して何か便利なことをすることを余儀なくされましたが、機能が制限されています。

この状況について考えてみましょう:

int getI() { return 5;}
int x& = getI();

x++; // Note x is an alias to a variable. What variable are you updating.

この一時オブジェクトの寿命を延ばすことは非常に混乱するでしょう。
次は:

int const& y = getI();

使用して理解することが直感的であるコードを提供します。

値を変更する場合は、値を変数に返す必要があります。関数からオブジェクトをコピーして戻すコストを回避しようとしている場合(オブジェクトはコピーで構築されているようです(技術的にはそうです))。次に、コンパイラが「戻り値の最適化」に非常に優れていることを気にしないでください


3
「それで、残っている唯一の問題は、なぜ規格が一時の参照を許可してステートメントの終わりを超えてオブジェクトの寿命を延ばしたくないのかということです。」それだ!あなた私の質問を理解しています。しかし、私はあなたの意見に同意しません。あなたは「コンパイラを非常に難しくする」と言いますが、それはconst参照のために行われました。サンプルでは、​​「注xは変数のエイリアスです。どの変数を更新していますか」と言います。問題ない。一意の変数があります(一時的)。一部の一時オブジェクト(5に等しい)は変更する必要があります。
Alexey Malistov

@Martin:ぶら下がっている参照は、整然としているだけではありません。メソッドの後半でアクセスすると、深刻なバグにつながる可能性があります。
mmmmmmmm

1
@Alexey:const参照にバインドすることで一時の有効期間が延長されるという事実は意図的に追加された例外であることに注意してください(手動最適化を可能にするためにTTBOMK)。非const参照に一時をバインドすることはプログラマーのエラーである可能性が最も高いと見なされたため、非const参照に追加された例外はありませんでした。
sbi 2009年

1
@alexy:見えない変数への参照!直感的ではありません。
マーティンヨーク

const_cast<x&>(getX());意味がありません
curiousguy

10

なぜ C ++のFAQで議論されているの(鉱山の太字):

C ++では、非const参照は左辺値にバインドでき、const参照は左辺値または右辺値にバインドできますが、非const右辺値にバインドできるものはありません。これは、新しい値が使用される前に破棄される一時的な値を変更することから人々を保護するためです。例えば:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

そのincr(0)が許可された場合、誰も見たことのない一時的な値が増加するか、さらに悪いことに、0の値は1になります。後者はばかげているように聞こえますが、実際には、値0を保持するためのメモリ位置は別として。


そのFortranの「バグゼロ」に噛まれたプログラマーの顔を見て面白かったでしょう。x * 0 与えるx?何?何??
ジョンD

その最後の議論は特に弱いです。言及する価値のあるコンパイラが、実際に0の値を1に変更したりincr(0);、そのような解釈をしたりすることはありません。明らかに、これが許可されている場合、一時的な整数を作成してそれを渡すと解釈されますincr()
user3204459

6

主な問題は

g(getx()); //error

は論理エラーです:gは結果を変更してgetx()いますが、変更されたオブジェクトを調べる機会がありません。gパラメータを変更する必要がない場合は、左辺値参照は必要ありません。値またはconst参照でパラメータを取得することができます。

const X& x = getx(); // OK

式の結果を再利用する必要がある場合があるため有効です。一時オブジェクトを扱っていることは明らかです。

しかし作ることは不可能です

X& x = getx(); // error

有効にすることなく有効にしg(getx())ます。これは、言語デザイナーがそもそも避けようとしていたことです。

g(getx().ref()); //OK

メソッドはのconst-nessのみを認識thisし、左辺値または右辺値のどちらで呼び出されたかがわからないため、有効です。

C ++ではいつものように、このルールの回避策はありますが、明示的にすることで、何をしているのかをコンパイラに知らせなければなりません。

g(const_cast<x&>(getX()));

5

これが許可されない理由についての最初の質問のように明確に回答されているようです:「それはおそらくエラーであるからです」。

FWIW、良いテクニックだとは思いませんが、どうすればできるを見せたいと思いました。

非const参照を取得するメソッドに一時変数を渡したい場合がある理由は、呼び出し側のメソッドが気にしない参照によって返される値を意図的に破棄するためです。このようなもの:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

以前の回答で説明したように、それはコンパイルされません。しかし、これは(私のコンパイラで)正しくコンパイルされて機能します:

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

これは、キャストを使用してコンパイラーに嘘をつくことができることを示しています。明らかに、未使用の自動変数を宣言して渡す方がずっときれいです。

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

この手法は、不要なローカル変数をメソッドのスコープに導入します。混乱やエラーを避けるためなど、なんらかの理由で後でメソッドで使用されないようにする場合は、ローカルブロックで非表示にすることができます。

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

-クリス


4

なぜあなたはこれまでにしたいX& x = getx();ですか?使用X x = getx();してRVOに依存するだけです。


3
電話g(getx())ではなく電話をしたいのでg(getx().ref())
アレクセイマリストフ2009年

4
@Alexey、それは本当の理由ではありません。これを行うと、論理的なエラーが発生gします。これは、もう手に入れることができないものを変更するためです。
ヨハネスシャウブ-litb 2009年

3
@ JohannesSchaub-litb多分彼は気にしない。
curiousguy

RVOに依存する」。ただし、「RVO」とは呼ばれません。
curiousguy

2
@curiousguy:それは非常に受け入れられている用語です。「RVO」と呼んでも間違いはありません。
パピー


3

すばらしい質問です。これはより簡潔な回答の私の試みです(多くの有用な情報がコメントにあり、ノイズを掘り下げるのが難しいため)。

一時に直接バインドれた参照は、その寿命を延長します[12.2.5]。一方、別の参照で初期化された参照は(最終的に同じ一時的であっても)しません。これは理にかなっています(コンパイラは、その参照が最終的に何を参照するかを知りません)。

しかし、この全体の考えは非常に混乱しています。たとえばconst X &x = X();、一時的なx参照は参照と同じ長さになりますが、実際にはconst X &x = X().ref();ref()が返されるかはわかりません。後者の場合、Xこの行の最後でのデストラクタが呼び出されます。(これは重要なデストラクタで観察できます。)

したがって、それは一般的に混乱し危険なようです(なぜオブジェクトのライフタイムに関するルールが複雑になるのでしょうか)。しかし、おそらくconst参照の必要性があったので、標準はこの動作を設定しています。

[ sbiコメントから]:const参照にバインドすると一時の有効期間が長くなるという事実は、意図的に追加された例外であることに注意してください(手動最適化を可能にするためにTTBOMK)。非const参照に一時をバインドすることはプログラマーのエラーである可能性が最も高いと見なされたため、非const参照に追加された例外はありませんでした。

すべての一時変数は、完全な式が終了するまで存続します。ただし、それらを使用するには、で使用するようなトリックが必要ですref()。それは合法です。異常なことが起こっていること(つまり、変更がすぐに失われる参照パラメーター)をプログラマーに思い出させることを除いて、余分なフープが飛び越える正当な理由はないようです。

[別のsbiコメント] Stroustrupが非const参照への右辺値のバインドを禁止する理由を(D&Eで)与える理由は、Alexeyのg()がオブジェクトを変更する場合(非constを取る関数から期待される)参照)、死ぬことになるオブジェクトを変更するため、変更された値を誰も取得できません。これはおそらく誤りであると彼は言う。


2

「上記のサンプルでは、​​非定数関数の呼び出しが許可されているため、一時オブジェクトが定数でないことは明らかです。たとえば、ref()は一時オブジェクトを変更できます。」

この例では、getX()はconst Xを返さないので、X()。ref()を呼び出すのとほぼ同じ方法でref()を呼び出すことができます。非const refを返すため、非constメソッドを呼び出すことができますが、実行できないのは、refを非const参照に割り当てることです。

SadSidosのコメントとともに、これはあなたの3つのポイントを不正確にします。


1

アレクセイが求めていることを実行したい場合に共有したいシナリオがあります。Maya C ++プラグインでは、ノードアトリビュートに値を取得するために、次の操作を実行する必要があります。

MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);   
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);

これは面倒なので、次のヘルパー関数を作成しました。

MPlug operator | (MFnDependencyNode& node, MObject& attribute){
    MStatus status;
    MPlug returnValue = node.findPlug(attribute, &status);
    return returnValue;
}

void operator << (MPlug& plug, MDoubleArray& doubleArray){
    MStatus status;
    MFnDoubleArrayData doubleArrayData;
    MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status);
    status = plug.setValue(doubleArrayObject);
}

そして今、私は投稿の最初からコードを次のように書くことができます:

(myNode | attributeName) << myArray;

|から返された一時変数をバインドしようとしているため、Visual C ++の外部でコンパイルされないことが問題です。<<演算子のMPlug参照への演算子。このコードは何度も呼び出され、MPlugをあまりコピーしないほうがよいので、参考にしてください。2番目の関数が終了するまで、一時オブジェクトが必要です。

さて、これは私のシナリオです。ちょうど私がアレクセイが説明することをしたい例を示すと思いました。私はすべての批評と提案を歓迎します!

ありがとう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.