【问题标题】:what is a subexpression of an initialization什么是初始化的子表达式
【发布时间】:2020-12-22 16:55:09
【问题描述】:
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 表达式,则复制捕获的实体的初始化以及 init-capture 的初始化器的组成表达式,
  4. 如果 e 是函数调用或隐式调用函数,调用中使用的每个默认参数的组成表达式,或
  5. 如果 e 创建一个聚合对象,初始化中使用的每个默认成员初始化器 ([class.mem]) 的组成表达式。

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

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

immediate subexpression of immediate subexpression... of immediate subexpression of ee 的子表达式。

由于第四个项目符号,我同意表达式 throw Y() 是函数 B(B&&, int = (throw Y(), 0)) noexcept 的子表达式。但是,我不知道B(B&&, int = (throw Y(), 0)) noexcept 是否被视为由表达式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 的措辞?所以,我的问题是:

第一季度:

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

第二季度:

如果第一个问题的答案是否定的,那a subexpression of such an initialization怎么解释?

【问题讨论】:

  • AFAIU,在您的第二个示例中,您没有implicitly 调用test 的构造函数,您实际上要求创建对象。另一方面,当您(或编译器)定义D​::​D(D&&) 时,即使您只是执行D​::​D(D&&){},它也会隐式 调用基类的构造函数。我认为这里的区别
  • @MartinMorterol 在我的第二个示例中,我只定义了一个Test 类型的对象,我既没有显式调用它的构造函数,也没有调用它的析构函数。 IIUC,隐式调用意味着没有相应的函数调用,其中后缀表达式是函数名。
  • “我没有显式调用构造函数”,我不同意(我可能错了),对我来说Test t{}; 是通过调用构造函数来定义和初始化t。为了加强我的观点,如果我们定义explicit Test(){},第二个例子仍然可以编译。
  • @MartinMorterol 注意Test t{};,我没有为t 显式调用构造函数。对于Test t{},其中{} 是一个初始化器,即braced-list-init。这与将构造函数定义为explicit Test(){} 无关,代码仍然格式正确。因为初始化是直接列表初始化。相反,Test t = {}; 格式错误。

标签: c++ initialization c++17 language-lawyer exception-specification


【解决方案1】:

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

为了了解初始化的子表达式是什么,您需要考虑编译器生成的代码。您对func() 的调用不会在调用范围内构造T 类型的对象,因为编译器不会将任何内容注入main 的范围内。该对象是在func 的范围内构造的,这不算。

【讨论】:

  • 您没有解释什么是“这种初始化的子表达式”
猜你喜欢
  • 1970-01-01
  • 2021-02-06
  • 1970-01-01
  • 1970-01-01
  • 2019-03-18
  • 2011-04-28
相关资源
最近更新 更多