参照型の変換演算子:トラブルを求めていますか?
-
22-07-2019 - |
質問
g++
class A {};
void foo(A&) {}
int main()
{
foo(A());
return 0;
}
次のエラーメッセージが表示されます。
> g++ test.cpp -o test
test.cpp: In function ‘int main()’:
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’
いくつかの考察の後、これらのエラーは私にとって十分に理にかなっています。 A()
は一時的な値であり、スタック上の割り当て可能な場所ではないため、アドレスを持たないようです。アドレスがない場合、参照を保持できません。いいでしょう
しかし、待って!次の変換演算子をクラスA
class A
{
public:
operator A&() { return *this; }
};
それですべて順調です!私の質問は、これがリモートでも安全かどうかです。 this
が一時的な値として構築されるとき、const
は正確に何を指しますか?
次の事実に自信があります
void foo(const A&) {}
は、const A&
および私が使用した他のすべてのコンパイラに従って一時的な値を受け入れることができます。 A&
キーワードはいつでもキャストできます。そのため、<=>パラメーターと<=>パラメーターの間に実際のセマンティックの違いがある場合、私は驚きます。それは私の質問をする別の方法だと思います:なぜ<=>一時的な値への参照はコンパイラによって安全と見なされますが、非<=>参照はそうではありませんか?
解決
アドレスを取得できないわけではありません(コンパイラは常にスタックに押し込むように命令することができますが、ref-to-constで行います)。これはプログラマの意図の問題です。 A <!> amp;をとるインターフェースでは、<!> quot;と言います。関数呼び出し<!> quot;の後に読み取ることができるように、このパラメーターの内容を変更します。一時的なものを渡すと、<!> quot; modified <!> quot;関数の後には存在しません。これは(おそらく)プログラミングエラーであるため、許可されていません。たとえば、次を考慮します。
void plus_one(int & x) { ++x; }
int main() {
int x = 2;
float f = 10.0;
plus_one(x); plus_one(f);
cout << x << endl << f << endl;
}
これはコンパイルされませんが、テンポラリーがref-to-non-constにバインドできる場合、コンパイルされますが、驚くべき結果が得られます。 plus_one(f)では、fは暗黙的に一時intに変換され、plus_oneはtempを取得してインクリメントし、基になるfloat fをそのままにします。 plus_oneが戻ったとき、効果はなかったでしょう。これはほぼ確実にプログラマが意図したものではありません。
ルールは時々混乱します。一般的な例(こちらで説明) 、ファイルを開いて何かを印刷し、閉じようとしています。できるようにしたい:
ofstream("bar.t") << "flah";
しかし、operator <!> lt; <!> lt; ref-to-non-constを取ります。オプションは、2行に分割するか、ref-to-non-constを返すメソッドを呼び出します:
ofstream("bar.t").flush() << "flah";
他のヒント
const参照にr値を割り当てると、参照が破棄されるまで一時が破棄されないことが保証されます。非const参照に割り当てると、そのような保証は行われません。
int main()
{
const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope.
A& a1 = A(); // You can't do this.
}
const-ness willy nillyを安全に捨て去り、動作することを期待することはできません。 const参照と非const参照には異なるセマンティクスがあります。
一部の人々が遭遇する可能性のある落とし穴:MSVCコンパイラー(Visual Studioコンパイラー、Visual Studio 2008で検証済み)は、このコードを問題なくコンパイルします 。通常、1つの引数(ダイジェストするデータのチャンク)を必要とする関数のプロジェクトでこのパラダイムを使用していましたが、チャンクを検索し、呼び出し元に結果を返すこともありました。もう1つのモードは、3つの引数を取ることで有効になりました。2番目の引数は検索する情報(デフォルトでは空の文字列への参照)で、3番目の引数は戻りデータ(デフォルトでは空のリストへの参照)です。
このパラダイムはVisual Studio 2005および2008で機能し、g ++でコンパイルするために所有者が呼び出して変更するのではなく、リストを作成して返すようにリファクタリングする必要がありました。
MSVCでこの種の動作を許可しないように、またはg ++で許可するようにコンパイラスイッチを設定する方法がある場合は、わくわくします。 MSVCコンパイラの許容性/ g ++コンパイラの制限性により、コードの移植が複雑になります。