VS 2012中的条件运算符类型转换

时间:2013-05-29 08:15:52

标签: c++ visual-c++ visual-studio-2012 c++11

我目前正在将一个相当大的项目从VS 2008转换到2012年,并且遇到了一个问题,即条件运算符类型转换的执行方式发生了变化。

首先让我说我接受条件运算符的语义有点复杂,并且意识到代码原来做的可能不正确,但我真的很困惑现在在VS 2012中发生的事情我想知道如果有人能够确切地解释它为什么会这样做。

class DummyString
{
    wchar_t wchBuf[32];

public:
    DummyString() { *wchBuf = 0; }
    DummyString(int) { *wchBuf = 0; }
    DummyString(const DummyString& ds) { *wchBuf = 0; }

    operator const wchar_t*() const { return wchBuf; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    DummyString ds;
    // note: the argc test is simply to stop the conditional operator
    // being optimised away
    const wchar_t* pPtr = (argc == 100) ? 0 : ds;
    assert(pPtr == static_cast<const wchar_t*>(ds));
    return 0;
}

在VS 2008中,上面的条件运算符将导致在operator const wchar_t*()上调用ds方法,并且不会触发断言。也就是说,它会隐式地将ds投射到const wchar_t*

在VS 2012中,条件运算符会导致以下行为:

  • 通过复制构造函数构建临时DummyString
  • 然后在该临时副本
  • 上执行对const wchar_t*的强制转换

这会导致pPtr指向一个被破坏的对象,并且当然会触发断言。

现在,如果我从类中删除DummyString(int)构造函数,代码无法在VS2012中编译(没有从'DummyString'转换为'int'),所以很明显条件中的0 导致表达式被计算为int而不是指针。

但在这种情况下,为什么不调用DummyString(int)构造函数将0转换为DummyString? 为什么编译器会创建ds的副本然后将其转换为wchar_t *,何时可以轻松地对原始对象执行强制转换?

我很想得到开悟! :)

2 个答案:

答案 0 :(得分:13)

C ++ 11标准的第5.16 / 3段规定:

  

[...]如果第二个和第三个操作数具有不同的类型并且具有(可能是cv-qualified)类   类型,或者如果两者都是相同值类别和相同类型的glvalues,除了cv-qualification, an   尝试将每个操作数转换为另一个操作数的类型。 [...]

那:

  

[...]如果两者都可以   转换,或者一个可以转换,但转换是模糊的,程序是不正确的。如果没有   如果可以转换,操作数保持不变,并进行如下所述的进一步检查。   如果只能进行一次转换,则该转换将应用于所选操作数并进行转换   操作数用于代替本节其余部分的原始操作数。

在这种情况下,只有从intDummyString的转换是可能的,因为在另一个方向上,const wchar_t*是我们可以做到的 - 没有标准的隐式转换从const wchar_t*int

这就是为什么编译器会抱怨你删除转换构造函数为int的原因:如果那个构造函数不存在,按照上面的段落,程序就会格式不正确(两者都不存在转换)方向)。

因此,第二个操作数(第一个选项)被认为是DummyString(0)

但是,第二个操作数可以转换为DummyString的事实意味着将根据第二个操作数进行评估。这取决于条件,条件评估为false(除非您将100个参数传递给命令行),这解释了为什么您没有看到对该构造函数的调用。根据第5.16 / 1段:

  

条件表达式从右到左分组。第一个表达式在上下文中转换为bool(第4条)。   它被评估,如果是,则条件表达式的结果是第二个表达式的值,   否则第三个表达。 仅评估第二个和第三个表达式中的一个。 [...]

但是为什么你的断言失败呢?

  

为什么编译器会创建ds的副本,然后将其强制转换为wchar_t*,何时可以轻松地对原始对象执行强制转换?

嗯,这是由于第5.16 / 4-5段:

  

如果第二个和第三个操作数是相同值类别的glvalues并且具有相同的类型,[...]

     

否则,结果是prvalue 。如果第二个和第三个操作数不具有相同的类型,则任何一个   具有(可能是cv限定的)类类型,重载决策用于确定转换(如果有)   适用于操作数(13.3.1.2,13.6)。

0不是glvalue,因此条件的结果将是prvalue。这意味着当条件为false时对条件运算符的求值将最终从ds构造临时,这是您观察到的行为。

这是第5.16 / 6段规定的,其中说:

  

执行Lvalue-to-rvalue(4.1),array-to-pointer(4.2)和函数到指针(4.3)标准转换   在第二和第三个操作数上。在这些转换之后,以下之一应该成立:

     

- 第二和第三个操作数具有相同的类型;结果是那种类型。 如果操作数有   类类型,结果是结果类型的prvalue临时值,它是从任一个复制初始化的   第二个操作数或第三个操作数取决于第一个操作数的值。 [...]

条件`“第二个和第三个操作数具有相同的类型”成立,因为在 5.16 / 3中描述的转换后,操作数现在被视为(参见这个答案的开头)。

要解决您的问题,您可以对第二个参数执行显式强制转换:

const wchar_t* pPtr = (argc == 100) ? 0 : static_cast<const wchar_t*>(ds);

由于存在从0到指针类型的标准转换并导致空指针 - 请参阅第4.10 / 1段。

答案 1 :(得分:0)

如果需要,可以通过提供以下内容来避免调用复制构造函数:

const wchar_t* pPtr = (argc == 100) ? 0 : (pPtr = ds);