3

現在、Borland C++-Builder 5 および 6 を使用して何年も開発してきたプロジェクトの 1 つを、最新の Embarcadero C++-Builder XE 3 Update 2 に移植しています。XE 3 は、新しい C++11 の一部をサポートしています。前者は非常に古いコンパイラを使用していたため、もちろん私にとっては完全に新しい右辺値参照のようなものです。プロジェクトをコンパイル可能にするために必要な変更はごくわずかでしたが、実行時に、新しい右辺値参照と移動セマンティクスの結果と思われる 1 つの問題に直面しています。

次のような三項演算子でこのフィールドを使用して、1 つのメソッドからのみ読み取られるパスを格納する std::wstring 型のフィールドを持つクラスがあります。

std::wstring retVal = someCondition ? this->classField : Util::doSomething(someArg);

someCondition は std::wstring.empty() への単なる呼び出しであり、Util::doSomething は std::wstring を値として返します。参照も関与もせず、データをコピーしただけです。

XE 3 では、someCondition が初めて true に評価された後、retVal は classField のコンテンツで適切に満たされますが、その後 classField のコンテンツは空になります。デバッガーを使用すると、右辺値を参照する最適化された代入演算子の実行を追跡できます。

 #if _HAS_RVALUE_REFERENCES
[...]
    _Myt& operator=(_Myt&& _Right)
        {   // assign by moving _Right
        return (assign(_STD forward<_Myt>(_Right)));
        }

    _Myt& assign(_Myt&& _Right)
        {   // assign by moving _Right
[...]

右辺値参照について読んだことは、私の問題を完全に説明するものであり、classField が右辺値として扱われる理由を説明する 2 つのコメントさえ見つけました。

https://stackoverflow.com/a/8535301/2055163

https://stackoverflow.com/a/6957421

classField の手動コピーを 1 つ追加して、上記の行を修正できます。

std::wstring retVal = someCondition ? std::wstring(this->classField) : Util::doSomething(someArg);

しかし、これはコンパイラの助けを借りずに移植したい各プロジェクトの三項演算子 (左辺値と右辺値が混在する場所) のすべての使用法を確認する必要があるという問題を解決しません。または発生しない場合があります。

私が理解していないのは、三項演算子の使用法に何か問題や悪い習慣があるかどうかです。これらのケースを検出するためのより良い解決策はありますか? 最適化をだますためだけに、一部のオブジェクトを手動でコピーする必要があるのはなぜですか? これは本当に意図した動作であり、ほとんどのコードベースで問題はありませんか? そのような問題にどのように対処しますか?

私は今どのように進行するかについて少し混乱しています。提案や説明があれば本当に感謝しています. ありがとう!


次の2つのケースをテストしました。

http://ideone.com/mWxxK3

最初の例は私の場合と似ていますが、グローバル文字列を格納するためにクラス インスタンスが使用されていないことを期待してください。dummy2 は適切に入力され、ダミーはその内容を保持します。

std::wstring retVal = someCondition ? this->classField : doSomethingResult;

上記の行を変更して、三項演算子を使用する前に Util:... の結果を std::wstring に保存して右辺値を削除すると、すべてが期待どおりに機能します。retVal にはそのコンテンツがあり、this->classField にもそのコンテンツが保持されます。

今の結論は?:-/

4

1 に答える 1

4

コンパイラのバグのようです。規格の関連セクションは5.16p6です。

[If]2番目と3番目のオペランドのタイプは同じです。結果はそのタイプです。オペランドがクラスタイプの場合、結果は結果タイプの一時的なprvalueであり、第1オペランドの値に応じて、第2オペランドまたは第3オペランドのいずれかからコピー初期化されます。


Util::doSomething(someArg)値の代わりに返される場合はstd::string&&、セクション5.16p3が必要です。

それ以外の場合、2番目と3番目のオペランドのタイプが異なり、いずれかが(おそらくcv修飾された)クラスタイプである場合、または両方が同じ値カテゴリのglvalueであり、cv修飾を除いて同じタイプである場合、それぞれを変換しようとします。それらのオペランドを他のタイプに変換します。タイプのオペランド式をE1タイプT1のオペランド式と一致するように変換できるかどうかを判断するプロセスは、次のように定義されます。E2T2

  • が左辺値である場合E2:変換で参照が左辺値に直接バインドする必要があるという制約を条件として、「左辺値参照先」タイプに暗黙的に変換できる場合E1に一致するように変換できます。E2E1T2
  • がx値の場合E2:参照が直接バインドする必要があるという制約を条件として、「右辺値参照先」タイプに暗黙的に変換できる場合E1に一致するように変換できます。E2E1T2
  • が右辺値である場合E2、または上記の変換のいずれも実行できず、オペランドの少なくとも1つが(おそらくcv修飾された)クラスタイプを持っている場合:
    • とがクラスタイプを持ち、基になるクラスタイプが同じであるか、一方が他方の基本クラスである場合:E1のクラスが、のクラスと同じタイプであるか、の基本クラスである場合、一致するように変換できます。のcv-qualificationは、のcv-qualificationと同じかそれよりも大きいcv-qualificationです。変換が適用される場合、は、から型の一時をコピー初期化し、その一時を変換されたオペランドとして使用することにより、型のprvalueに変更されます。E2E1E2T2T1T2T1E1T2T2E1
    • それ以外の場合(つまり、E1またはE2が非クラスタイプを持っている場合、または両方にクラスタイプがあるが、基になるクラスが同じでないか、一方が他方の基本クラスでない場合):暗黙的にタイプに変換できる場合はE1、一致するように変換できますその式は、prvalue(または、prvalueの場合はその型)に変換された場合に得られます。E2E1E2E2E2

4番目の箇条書きが適用されるため、ソースコードを変更することなく、コピーを自動的に作成する必要があります。


どちらの場合も、作成時に一時コピーを安全に移動できretvalます。

于 2013-02-08T18:11:56.190 に答える