我目前正在将一个相当大的项目从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 *,何时可以轻松地对原始对象执行强制转换?
我很想得到开悟! :)
答案 0 :(得分:13)
C ++ 11标准的第5.16 / 3段规定:
[...]如果第二个和第三个操作数具有不同的类型并且具有(可能是cv-qualified)类 类型,或者如果两者都是相同值类别和相同类型的glvalues,除了cv-qualification, an 尝试将每个操作数转换为另一个操作数的类型。 [...]
那:
[...]如果两者都可以 转换,或者一个可以转换,但转换是模糊的,程序是不正确的。如果没有 如果可以转换,操作数保持不变,并进行如下所述的进一步检查。 如果只能进行一次转换,则该转换将应用于所选操作数并进行转换 操作数用于代替本节其余部分的原始操作数。
在这种情况下,只有从int
到DummyString
的转换是可能的,因为在另一个方向上,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);