代码之家  ›  专栏  ›  技术社区  ›  xmh0511

什么是初始化的子表达式

  •  0
  • xmh0511  · 技术社区  · 4 年前
    struct B {
      B() throw();
      B(const B&) = default;        // implicit exception specification is noexcept(true)
      B(B&&, int = (throw Y(), 0)) noexcept;
      ~B() noexcept(false);
    };
    int n = 7;
    struct D : public A, public B {
        int * p = new int[n];
        // D​::​D() potentially-throwing, as the new operator may throw bad_­alloc or bad_­array_­new_­length
        // D​::​D(const D&) non-throwing
        // D​::​D(D&&) potentially-throwing, as the default argument for B's constructor may throw
        // D​::​ D() potentially-throwing
    };
    

    考虑上面引用的代码 except.spec#11 我对D的所有构造函数的例外规范毫无疑问,除了 D::D(D&&) ,遵循以下规则:

    类X的隐式声明构造函数,或在其第一个声明中默认的没有noexcept说明符的构造函数,如果且仅当以下任何构造可能引发异常时,才具有潜在的引发异常规范:

    • 通过重载解析在类X的构造函数隐式定义中选择的构造函数,用于初始化潜在构造的子对象,或
    • 这种初始化的子表达式 ,例如默认参数表达式,或者,
    • 对于默认构造函数,默认成员初始化器。

    显然,制定规则 D​::​D(D&&) 具有潜在抛出异常的规范既不是第一个项目符号,也不是第三个项目符号。对于第一条规则,所选构造函数用于初始化类型为的基子对象 B B(B&&, int = (throw Y(), 0)) noexcept ,声明其具有非抛出异常规范。第三条规则适用于默认构造函数。因此,只有第二条规则适用于这种情况。

    然而, D: :D(D&) 除了表达式 throw Y() 被认为是 D: :D(D&) .

    定义直接子表达式的规则如下:

    表达式e的直接子表达式是

    1. e操作数的组成表达式
    2. e隐式调用的任何函数调用 ,
    3. 如果e是lambda表达式,则由copy捕获的实体的初始化和init初始化器的组成表达式被捕获,
    4. 如果e是函数调用或隐式调用函数, 每个默认参数的组成表达式 在通话中使用,或
    5. 如果e创建了一个聚合对象,则初始化中使用的每个默认成员初始化器([class.mem])的组成表达式。

    表达式e的子表达式是e的直接子表达式,或者是e的立即子表达式的子表达式。

    理解子表达式规则的简单方法是它递归工作,即,

    immediate subexpression of immediate subexpression... of immediate subexpression of e 是以下的子表达式 e .

    我同意这个表达 投掷Y() 是函数的子表达式 B(B&,int=(throw Y(),0))除此之外 由于第四颗子弹。然而,我不知道是否 B(B&,int=(throw Y(),0))除此之外 被视为 隐式调用函数 由表达式调用 D: :D(D&) ,这似乎服从了第二颗子弹。如果是这样,请考虑以下代码:

    class Test{
      Test(){}
      ~Test(){}
    };
    void func(){
      Test t{} // implicitly invoke the defautl constructor of `Test`
      // would implicitly  invoke the destructor of `Test`
    }
    int main(){
      func();
    }
    

    所以,正如我在注释中写的那样,构造函数和析构函数是 Test 被视为表达的子表达 func() ?如果不是,如何解释措辞 a subexpression of such an initialization 那么,我的问题是:

    Q1:

    在第二个示例中,隐式调用的构造函数或析构函数是否被视为表达式的子表达式 func() ?

    问题2:

    如果第一个问题的答案是否定的,那么如何解释 这种初始化的子表达式 ?

    0 回复  |  直到 4 年前
        1
  •  0
  •   Brian Bi    2 年前

    在[excel.spec]/7.2中,特别是“这种初始化的子表达式”的措辞,什么是“这样的初始化”?答案是,它必须参考第7.1节中描述的初始化:

    类构造函数隐式定义中通过重载解析选择的构造函数 X 初始化 潜在构造的子对象,或

    它不是指初始化 十、 .[excel.spec]/7开头的文本仅指的是 十、 ,而不是“初始化” 十、 阅读第7.2页的自然方式是,“这样的初始化”是指第7.1页中唯一提到的初始化。

    因此,在应用[excel.spec]/7.2时,这里的问题不是默认参数的组成表达式是否 B 的move构造函数是 D 初始化,而是它们是否是 B 初始化,答案是肯定的。

    虽然上面的解释应该回答你的主要问题,但我也要说,我不认为构造函数 B 被构造函数视为“隐式调用” D 为了[介绍执行]/10的目的。如果是,那么这意味着初始化 B 子对象是初始化的子表达式 D 相反,我认为我们应该考虑启动 B 在[intro.execution]/12.3下将子对象作为完整表达式:

    A. 充分表达 是[…]一个 init声明器 (第11条)或 mem初始化器 (15.6.2),包括 初始化器,[…]

    虽然措辞不明确,但我认为初始化 B 子对象被认为是由 mem初始化器 为了[介绍执行]的目的。[class.copy.coctor]/14定义了默认移动构造函数的行为,并没有明确表示基类和成员是由 mem初始化器 然而,如果移动构造函数的默认定义与相应的用户定义构造函数具有不同的子表达式层次结构,那就很奇怪了。

    如果我们将对基类构造函数的隐式调用视为不是 mem初始化器 ,而是一个“函数调用 e 隐式调用“在[intro.execution]/10.2下,这意味着前者不是一个完整的表达式,在它的末尾销毁临时对象没有意义。如果是这样的话,那真的很奇怪:这意味着在默认移动构造函数执行期间创建的临时对象与在等效用户提供的移动构造函数执行过程中创建的临时变量在不同的时间销毁 experiment on Godbolt 让我放心,GCC和Clang不会做出如此奇怪的解释。

    在第二个例子中,表达式 func() 不隐含地调用任何东西。在函数体中调用的函数 func() 不要计数。关于隐式调用的要点包括完整表达式末尾的临时对象的析构函数、为具体化临时对象而调用的构造函数、表达式所需的隐式转换所调用的构造函数和转换运算符等。

        2
  •  -1
  •   Christopher Yeleighton    4 年前

    对于第一条规则,所选构造函数用于初始化类型为的基子对象 B B (B&&, int = (throw Y(), 0)) noexcept ,声明其具有非抛出异常规范。这意味着构造函数 B 被召唤时不会投掷。然而,在 D 电话 B ,它必须从默认值生成第二个参数,这将抛出。因此, D 不会因为 B 但由于默认参数在外部执行 B .

    为了了解初始化的子表达式是什么,您需要考虑编译器生成的代码。你的呼唤 func() 不构造类型为的对象 T 在调用的范围内,没有任何东西被注入到 main 由编译器。该对象在以下范围内构建 func 这不算数。