工作小结 22.09.01

1. 歧义消除
C++中类似int(A)的代码会引起语法歧义,其可以解释为:

  • 对象声明,即两边具有冗余圆括号的对象声明,其等同于int A
  • 表达式语句,即函数式类型转换,其等同于(int)A
  • 函数类型声明,即A是类型名时,其等同于int (*fp)(A a)

对于表达式语句与对象声明的二义性,C++标准 8.9 Ambiguity resolution 中提到

1 There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration.
2 [Note: If the statement cannot syntactically be a declaration, there is no ambiguity, so this rule does not apply. The whole statement might need to be examined to determine whether this is the case. This resolves the meaning of many examples. [Example: Assuming T is a simple-type-specifier,

解释:
(1) 在涉及表达式语句和声明的语法中存在歧义,则将函数式型转解析为其最左边的子表达式的一个声明
(2) 若语法上不能是声明,则不存在歧义,解析为函数式类型转换
标准中的例子:

// Example: Assuming T is a simple-type-specifier
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

9.3.2 Ambiguity resolution 中还提到

1 The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 8.9 can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 8.9, the resolution is to consider any construct that could possibly be a declaration a declaration.

解释:
此歧义还会出现在声明的上下文中,例如函数类型的声明。在这种情况下,可在参数名称周围带有一组冗余括号的函数声明以函数样式强制转换作为初始值设定项的对象声明之间进行选择。解决方案是将任何可能是声明的构造视为声明。
即将int(A)优先考虑为对象声明,从而整体解析为参数名称周围带有一组冗余括号的函数声明
标准中的例子:

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 y((int)a);    // object declaration
    S z = int(a);   // object declaration
}

2 An ambiguity can arise from the similarity between a function-style cast and a type-id. The resolution is that any construct that could possibly be a type-id in its syntactic context shall be considered a type-id.

解释:
函数式类型转换和类型标识之间歧义。解决方案是,在其语法上下文中可能是类型标识的构造都应被视为类型标识。
即语法上优先视为类型标识,若不合法,则视为函数式类型转换表达式
标准中的例子:

template  struct X {};
template  struct Y {};
X a;                     // type-id
X b;                    // expression (ill-formed)
Y c;                     // type-id (ill-formed)
Y d;                    // expression

void foo(signed char a) {
    sizeof(int());              // type-id (ill-formed)
    sizeof(int(a));             // expression
    sizeof(int(unsigned(a)));   // type-id (ill-formed)

    (int()) + 1;                // type-id (ill-formed)
    (int(a)) + 1;               // expression
    (int(unsigned(a))) + 1;     // type-id (ill-formed)
}

3 Another ambiguity arises in a parameter-declaration-clause when a type-name is nested in parentheses. In this case, the choice is between the declaration of a parameter of type pointer to function and the declaration of a parameter with redundant parentheses around the declarator-id. The resolution is to consider the type-name as a simple-type-specifier rather than a declarator-id.

解释:
类型名嵌套在圆括号中,参数声明子句中会出现二义性。在这种情况下,可在声明函数指针类型的参数和对象声明周围带有冗余括号的参数之间进行选择。解决方法是将类型名称视为简单类型说明符。
即将int(A)称视为函数类型声明,其等同于int (*fp)(A a)
标准中的例子:

class C { };
void f(int(C)) { }  // void f(int(*fp)(C c)) { }
                    // not: void f(int C) { }
int g(C);
void foo() {
    f(1);           // error: cannot convert 1 to function pointer
    f(g);           // OK
}

注:上文提及的C++标准为 C++ 20 ISO/IEC 14882:2020 草案版本 N4849

2. 迭代器的operator++
在对关联类容器遍历进行删除时,会这样做:

for (auto iter = m_map.begin(); iter != m_map.end();)
{
    if (删除条件) {
        m_map.erase(iter++);
    } else {
        ++iter;
    }  
}

在对关联类容器使用erase()去删除时,会使当前迭代器失效,那上文中的m_map.erase(iter++);在删除之后,iter不就失效了吗?为什么还能iter++,指向容器中的后一个元素?
看下迭代器对++操作的重载实现:

// 以vector 迭代器为例,Windows版本如下
_Vector_const_iterator operator++(int) noexcept {
    _Vector_const_iterator _Tmp = *this;
    ++* this;
    return _Tmp;
}

可以看到实现中,用局部对象_Tmp指向了当前位置,然后对迭代器进行了++操作,最后返回了局部对象_Tmp
所以,iter++实际上已经先对迭代器进行了++操作(此时,迭代器还未失效),但最终返回了++前的值提供给erase()去删除。
从源码也可以看出,后置++需要多生成一个局部对象_Tmp,因此效率不及前置++。这也是为什么在循环中,一般对迭代器使用前置++的原因。

你可能感兴趣的:(工作小结 22.09.01)