web-dev-qa-db-ja.com

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

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
218
Alexey Malistov

コードで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++標準には右辺値参照が含まれることに注意してください。したがって、参照として知っているものは、「左辺値参照」と呼ばれるようになります。右辺値を右辺値参照にバインドでき、「右辺値」の関数をオーバーロードできます。

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
34
sbi

表示されているのは、演算子チェーンが許可されていることです。

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

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

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

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

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

これのより良い例については、この回答の最後をご覧ください

notは、一時的なものを参照にバインドできます。そうすると、式の最後で破棄されるオブジェクトへの参照が生成されるため、ぶら下がり参照(これは乱雑であり、標準は乱雑を好まない).

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

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

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

16
Martin York

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

9
Tony Delroy

主な問題は

g(getx()); //error

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

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

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

しかし、作ることはできません

X& x = getx(); // error

g(getx())を有効にせずに有効。これは、言語設計者がそもそも回避しようとしていたことです。

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

メソッドはthisのc​​onst-nessのみを知っているため、メソッドは左辺値または右辺値で呼び出されるかどうかを知らないため有効です。

C++の場合と同様に、このルールには回避策がありますが、明示的にすることで何をしているかをコンパイラーに知らせる必要があります。

g(const_cast<x&>(getX()));
6
Dan Berindei

whyについての元の質問のように思えますが、これは許可されていません:「エラーである可能性が高いため」。

FWIW、良いテクニックだとは思いませんが、howを表示できると思いました。

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

-クリス

5
Chris Pearson

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

4
paul

X& x = getx();が必要な理由は何ですか? X x = getx();を使用して、RVOに依存してください。

4
DevFred

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

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

しかし、この考え全体は非常に紛らわしいです。例えば。 const X &x = X();は、x参照が存在する限り一時的に最後になりますが、const X &x = X().ref();はそうではありません(誰がref()を実際に返したかを知っています)。後者の場合、Xのデストラクタがこの行の最後で呼び出されます。 (これは重要なデストラクタで確認できます。)

そのため、一般的に混乱し危険です(オブジェクトの有効期間に関するルールを複雑にしているのはなぜですか)が、少なくともconst参照が必要であるため、標準はこの動作を設定します。

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

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

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

3
DS.

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

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

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

2
Patrick

Alexeyが求めていることを何がしたいのかを共有したいシナリオがあります。 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番目の関数が終了するまで一時オブジェクトが必要なだけです。

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

ありがとう。

1