C ++:三元运算符中使用的右值引用似乎破坏了现有代码

时间:2013-02-08 17:45:00

标签: c++ c++11 ternary-operator move-semantics rvalue-reference

我目前正在移植我的一个项目,这个项目已经使用Borland C ++ - Builder 5和6开发多年,最新的Embarcadero C ++ - Builder XE 3 Update 2. XE 3支持一些新的C +由于前者使用了非常旧的编译器,因此对于我来说当然对我来说是全新的。我只需要很少的修改就可以使我的项目可编译,但在运行时我面临的一个问题似乎是新的rvalue引用和移动语义的结果。

我有一个类型为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被视为右值。

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

https://stackoverflow.com/a/6957421

我可以使用classField的另一个手册副本修复上面的行:

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

但这并没有解决我需要在没有编译器帮助的情况下查看每个项目我需要检查三元运算符(其中左值和右值混合)的每个用法的问题,因为它是运行时可能会或可能不会发生的问题。

我不明白的是,如果我使用三元运算符有错误或不好的做法?是否有更好的解决方案来检测这些案例?为什么我要手动复制一些对象只是为了欺骗优化?这是真正意图的行为,而且大部分代码库都没有问题吗?你是如何处理这些问题的?

我对如何立即进步感到困惑,并且非常感谢任何建议和/或解释。谢谢!


我测试了以下两个有效的案例:

http://ideone.com/mWxxK3

第一个示例与我的情况类似,期望没有类实例用于存储全局字符串。 dummy2被正确填充并且dummy保持其内容。

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

当我更改上面的行以通过在使用三元运算符之前将Util:...的结果保存到std :: wstring来删除rvalue时,一切都按预期工作。 retVal有它的内容,这个&gt; classField也保留了它。

现在结论是什么? : - /

1 个答案:

答案 0 :(得分:4)

看起来像编译器错误。标准的相关部分是5.16p6:

  

[If]第二个和第三个操作数具有相同的类型;结果是那种类型。如果操作数具有类类型,则结果是结果类型的prvalue临时值,它根据第一个操作数的值从第二个操作数或第三个操作数进行复制初始化。


如果Util::doSomething(someArg)返回std::string&&而不是值,我们需要第5.16p3节:

  

否则,如果第二个和第三个操作数具有不同的类型并且具有(可能是cv限定的)类类型,或者两者都是相同值类别的glvalues和除cv-qualification之外的相同类型,则尝试将每个操作数转换为另一个操作数的类型。确定类型E1的操作数表达式T1是否可以转换为匹配类型E2的操作数表达式T2的过程定义如下:

     
      
  • 如果E2是左值,则E1可以转换为匹配E2,如果E1可以隐式转换为“T2的左值引用” “,受限制,在转换中,引用必须直接绑定到左值。
  •   
  • 如果E2是xvalue:E1可以转换为匹配E2,如果E1可以隐式转换为“T2的右值引用” “,受限于引用必须直接绑定。
  •   
  • 如果E2是右值,或者上述两个转换都不能完成且至少有一个操作数具有(可能是cv-qualified)类类型:   
        
    • 如果E1E2具有类类型,并且基础类类型相同或者一个是另一个的基类:E1可以转换为匹配{{1如果E2的类与T2的类相同,或者是T1的类的基类,则T2的cv-qualification与cv-qualification相同,或者比T1的cv资格更高的cv资格。如果应用了转换,则通过从E1复制初始化T2类型的临时值并将该临时值用作转换后的操作数,将T2更改为类型E1的prvalue
    •   
    • 否则(即,如果E1E2具有非类型类型,或者它们都具有类类型但基础类不是相同的或一个是另一个的基类): E1可以转换为匹配E2,如果E1可以隐式转换为E2E2转换为prvalue时所具有的类型(或E2它的类型,如果retval是prvalue。)
    •   
  •   

第四个项目符号适用,因此应自动制作副本,不需要对源代码进行任何更改。


在这两种情况下,构建{{1}}时都可以安全地移动临时副本。