【发布时间】: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 的直接子表达式是
- e 的操作数的组成表达式
- e 隐式调用的任何函数调用,
- 如果 e 是 lambda 表达式,则复制捕获的实体的初始化以及 init-capture 的初始化器的组成表达式,
- 如果 e 是函数调用或隐式调用函数,调用中使用的每个默认参数的组成表达式,或
- 如果 e 创建一个聚合对象,初始化中使用的每个默认成员初始化器 ([class.mem]) 的组成表达式。
表达式 e 的子表达式是 e 的直接子表达式或 e 的直接子表达式的子表达式。
理解子表达式规则的简单方法是递归地工作,即
immediate subexpression of immediate subexpression... of immediate subexpression of e 是e 的子表达式。
由于第四个项目符号,我同意表达式 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