9

我目前正在将一个相当大的项目从 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转换为 a const wchar_t*

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

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

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

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

但是在那种情况下,为什么不DummyString(int)调用构造函数将 0 转换为 a DummyString?为什么编译器创建一个副本ds然后将其转换为 wchar_t*,而它可以很容易地对原始对象执行转换?

我很想开悟!:)

4

2 回答 2

13

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

[...] 如果第二个和第三个操作数具有不同的类型并且具有(可能是 cv 限定的)类类型,或者如果两者都是相同值类别和相同类型(除了 cv 限定)的 glvalue,则进行尝试将这些操作数中的每一个转换为另一个的类型。[...]

然后:

[...]如果两者都可以转换,或者一个可以转换但转换不明确,则程序格式错误。如果两者都不能转换,则操作数保持不变,并按如下所述执行进一步检查。 如果恰好可以进行一次转换,则该转换将应用于所选操作数,并且在本节的其余部分中使用转换后的操作数代替原始操作数。

在这种情况下,只有从intto的转换DummyString是可能的,因为在另一个方向上 aconst wchar_t*是我们能走的最远 - 没有从 aconst wchar_t*到 a 的标准隐式转换int

这就是为什么如果您删除带有int: 的转换构造函数,编译器会抱怨,如果该构造函数不存在,根据上面的段落,程序将是错误的(在任何一个方向都不会存在转换)。

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

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

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

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

为什么编译器会创建一个副本ds然后将其强制转换为wchar_t*,而它可以很容易地对原始对象执行强制转换?

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

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

否则,结果为纯右值。如果第二个和第三个操作数的类型不同,并且都具有(可能是 cv 限定的)类类型,则使用重载决议来确定要应用于操作数(13.3.1.2、13.6)的转换(如果有) .

0不是泛左值,因此条件的结果将是纯右值。这意味着在条件为时对条件运算符的评估false最终将构建一个临时from ds,这是您观察到的行为。

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

左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 标准转换在第二个和第三个操作数上执行。在这些转换之后,应满足以下条件之一:

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

条件“第二个和第三个操作数具有相同的类型”成立,因为在 5.16/3 中描述的转换之后现在正在考虑操作数(请参阅此答案的开头)。

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

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

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

于 2013-05-29T08:36:51.107 回答
0

If you want, you may avoid invoking the copy constructor by giving this:

const wchar_t* pPtr = (argc == 100) ? 0 : (pPtr = ds);
于 2013-05-29T08:50:05.200 回答