3

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

我有一个类,其类型为 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 来更改上面的行以删除右值时,一切都按预期工作。retVal 有它的内容,this->classField 也有它的内容。

现在的结论是什么?:-/

4

1 回答 1

4

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

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


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

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

  • ifE2是左值:E1可以转换为匹配E2ifE1可以隐式转换为“左值引用T2”类型,但受制于在转换中引用必须直接绑定到左值的约束。
  • ifE2是一个 xvalue:E1可以转换为匹配E2ifE1可以隐式转换为类型“rvalue reference to T2”,受引用必须直接绑定的约束。
  • IfE2是一个右值,或者上面的转换都不能完成,并且至少有一个操作数具有(可能是 cv 限定的)类类型:
    • ifE1E2具有类类型,并且基础类类型相同或一个是另一个的基类:如果该类与 的类的类型相同,或者是其基类,则E1可以转换为匹配,并且的 cv 限定与 的cv 限定相同或更高的 cv 限定。如果应用了转换,则通过复制初始化类型的临时值并将该临时值用作转换后的操作数,将其更改为类型的纯右值。E2T2T1T2T1E1T2T2E1
    • 否则(即,如果E1E2具有非类类型,或者如果它们都具有类类型但基础类不相同或不是另一个的基类):E1可以转换为匹配E2ifE1可以隐式转换为该类型如果将表达式转换为纯右值(或它具有的类型,如果是纯右值) ,则该表达式E2将具有。E2E2

第四个项目符号适用,因此应自动制作副本,无需更改源代码。


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

于 2013-02-08T18:11:56.190 回答