240

getx()関数が返す一時オブジェクトへの非 const 参照を取得できないのはなぜですか? 明らかに、これは 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

11 に答える 11

109

右辺値参照に関するこの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++ はしばしば奇妙です。

于 2009-10-14T11:57:56.787 に答える
42

コードgetx()では、一時オブジェクト、いわゆる「右辺値」を返します。右辺値をオブジェクト(別名変数)にコピーするか、それらを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++標準には右辺値参照が含まれることに注意してください。したがって、参照として知っているものは、「左辺値参照」と呼ばれるようになります。右辺値を右辺値参照にバインドすることが許可され、「右辺値」で関数をオーバーロードできます。

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
于 2009-10-14T13:19:13.657 に答える
17

あなたが示しているのは、演算子の連鎖が許可されているということです。

 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();

直感的に使用および理解できるコードを提供します。

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

于 2009-10-14T13:45:27.450 に答える
15

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 を保持するためのメモリ位置を確保します。

于 2015-12-30T10:05:29.647 に答える
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()));
于 2009-11-13T14:39:01.717 に答える
5

なぜこれが許可されないのかについての元の質問は、「エラーである可能性が高いため」と明確に回答されているようです。

FWIW、良いテクニックではないと思いますが、どうすればいいのかを見せたいと思いました。

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

// 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
}

-クリス

于 2013-02-22T00:54:45.953 に答える
4

邪悪な回避策には、「可変」キーワードが含まれます。実際に悪であることは、読者の練習問題として残されています。または、こちらをご覧ください:http ://www.ddj.com/cpp/184403758

于 2009-10-14T15:02:10.807 に答える
4

なぜあなたは今までしたいX& x = getx();ですか?X x = getx();RVO を使用して信頼するだけです。

于 2009-10-14T11:08:09.897 に答える
4

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

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

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

したがって、一般的には混乱を招き、危険に思えます (なぜオブジェクトの有効期間に関する規則を複雑にするのですか?) が、おそらく少なくとも const 参照が必要だったので、標準はそれらに対してこの動作を設定しています。

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

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

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

于 2012-04-08T19:26:47.493 に答える
2

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

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

SadSidosのコメントに加えて、これはあなたの3つのポイントを間違ったものにします。

于 2009-10-14T11:41:25.820 に答える
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 番目の関数が終了するまで存続する必要があるだけです。

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

ありがとう。

于 2013-06-22T16:51:40.233 に答える