【问题标题】:Most vexing parse and pointer indirection/dereferencing最令人头疼的解析和指针间接/解引用
【发布时间】:2013-08-02 10:17:58
【问题描述】:

最少的代码:

struct A {
  A(int = 0) {}
};

int i = 0, *p = &i;
int* foo () { return p; }

int main () {
  A(); // calls `A::A(int=0)`
  A(i);  // calls `A::A(int=0)`

  A(*p); // <--- (1) same as local `A *p;`
  {
    A((*p));   // <--- (2) same as local `A *p;`
  }
  A (*foo());  // <--- (3) ??
  {
    A ((*foo()));  // <--- (4) ??
  }
}

预计至少 A((*p)) 会调用 A::A(int=0)。即使在*p 周围加上多个大括号,也会将该语句视为A *p;
foo 相关语句也是如此,其中构造函数 A::A(int=0) 未被调用。 这是demo

问题

  1. 为什么甚至 (2) 和 (4) 都被视为声明?
  2. 语句(3)和(4)中foo的描述是什么?

【问题讨论】:

  • 顺便说一句,你知道A(i);A i; 是一样的,对吧?
  • 为什么(1)不创建带有*p的临时A对象传递给构造函数?
  • 声明中的括号会影响“解析方向”。这很有用的示例 - 将格式错误的 int&amp; a[5]; 与很好的 int (&amp;a)[5]; 进行比较。现在A(*p) 中的括号是多余的,因为A*p 的解析方式相同。因此,这是一个有效的声明。
  • 如何使用 *p 作为构造函数参数创建 A 类型的未命名临时对象?
  • (0, (A(*p));,例如。

标签: c++ pointers language-lawyer dereference most-vexing-parse


【解决方案1】:

在解析可能是声明或表达式的构造时 - 称为最令人烦恼的解析歧义 - 标准规定“解决方案是将任何可能是声明的构造视为声明”。

(2) 和 (4) 都是有效的声明,因此它们必须被解析为声明。 (3) 和 (4) 都声明了一个 foo 类型的函数 A*() 又名“函数不带参数返回指向 A 的指针”

6.8 Ambiguity resolution [stmt.ambig]

在涉及表达式语句和声明的语法中存在歧义:将函数样式显式类型转换 (5.2.3) 作为其最左边的子表达式的表达式语句与第一个声明符开头的声明无法区分a (. 在这些情况下,语句是声明。 [注意:为了消除歧义,可能必须检查整个语句以确定它是表达式语句还是声明。这消除了许多示例的歧义. [示例:假设 T 是简单类型说明符 (7.1.5),

T(a)->m = 7; // expression-statement
T(a)++; //expression-statement
T(a,5)<<c; //expression-statement
T(*d)(int); //declaration
T(e)[5]; //declaration
T(f) = { 1, 2 }; // declaration
T(*g)(double(3)); // declaration

在上面的最后一个例子中,指向 T 的指针 g 被初始化为 double(3)。由于语义原因,这当然是错误的,但这并不影响句法分析。 ——结束示例]

8.2 Ambiguity resolution [dcl.ambig.res]

由于函数样式转换和 6.8 中提到的声明之间的相似性而产生的歧义也可能出现在声明的上下文中。在这种情况下,选择是在参数名称周围带有一组冗余括号的函数声明和以函数样式强制转换作为初始值设定项的对象声明之间进行选择。正如 6.8 中提到的歧义一样,解决方案是将任何可能是声明的构造视为声明。 [注意:声明可以通过非函数式强制转换、= 表示初始化或删除参数名称周围的冗余括号来明确消除歧义。 ] [例子:

struct S {
    S(int);
};

void foo(double a)
{
    S w(int(a)); // function declaration
    S x(int()); // function declaration
    S y((int)a); // object declaration
    S z = int(a); // object declaration
}

——结束示例]

【讨论】:

    【解决方案2】:

    为什么连 (2) 和 (4) 都被视为声明?

    声明中的括号可用于更改声明符中的关联顺序,并可能将构造的含义从声明更改为表达式。它们与[] 具有相同的优先级,并且从左到右分组。

    例如:

    int*a[1];   // brackets have higher precedence - this is an array of pointers
    int(*a)[1]; // pointer to an array
    

    现在,如果您考虑A*p;A(*p);,这里的括号是多余的,因为它们并没有改变解析的方式。添加更多它们不会改变任何事情 - 它仍然是一个有效的声明。

    语句(3)和(4)中对foo的描述是什么?

    声明同A* foo();

    【讨论】:

    • 这不是在回答问题。括号在某些情况下确实很重要(最令人烦恼的解析)。例如假设有一个class X,其构造函数为X::X(A);。现在,X x(A())X x((A())) 不同。前者将x 声明为函数,后者将x 声明为以A 作为参数的对象。我在这里期待同样的现象,我想取消引用 p 并调用构造函数 A::A(int)
    • 你说得对,我的措辞很糟糕。我的意思是,这个特定结构 (A(*p);) 中的括号首先是多余的(添加更多括号并不会改变任何事情)。它们只在使构造作为声明无效的地方才重要,如您的示例所示。
    • @iammilind 我改了措辞,现在更好了吗?
    • 其实你的答案是不同的方向。您可能可以参考有关most vexing parseand other one 的线程,这表明大括号对于编译器了解构造的性质是多么重要。我在想同样的规则也适用于指针解引用,但这里没有发生。
    • @iammilind 我确定我不理解你。关键是它首先不是指针解引用。是的,它可以在句法上被解释为一种,但它也是一个有效的声明。如果两者兼而有之,则始终被视为声明。
    猜你喜欢
    • 2020-08-17
    • 2013-06-08
    • 1970-01-01
    • 2018-05-26
    • 2019-11-30
    • 1970-01-01
    • 2018-07-21
    • 1970-01-01
    相关资源
    最近更新 更多