在C ++ 17中的对象生存期之外调用非静态成员函数 [英] Calling non-static member function outside of object's lifetime in C++17

查看:116
本文介绍了在C ++ 17中的对象生存期之外调用非静态成员函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下程序在C ++ 17和更高版本中是否具有未定义的行为?

Does the following program have undefined behavior in C++17 and later?

struct A {
    void f(int) { /* Assume there is no access to *this here */ }
};

int main() {
    auto a = new A;
    a->f((a->~A(), 0));
}

C ++ 17保证在对调用的参数求值之前,对A对象的成员函数求值a->f.因此,来自->的间接定义是明确的.但是在输入函数调用之前,将对参数进行求值并结束A对象的生存期(但是,请参见下面的编辑内容).通话是否仍存在未定义的行为?这样可以在对象的生存期之外调用它的成员函数吗?

C++17 guarantees that a->f is evaluated to the member function of the A object before the call's argument is evaluated. Therefore the indirection from -> is well-defined. But before the function call is entered, the argument is evaluated and ends the lifetime of the A object (see however the edits below). Does the call still have undefined behavior? Is it possible to call a member function of an object outside its lifetime in this way?

a->f的值类别是> [expr. ref]/6.3.2 [basic.life]/7 只会禁止对 glvalues 的非静态成员函数调用,该调用引用了生存期对象.这是否表示通话有效? (正如评论中所讨论的,我可能会误会[basic.life]/7,它可能确实适用于此.)

The value category of a->f is prvalue by [expr.ref]/6.3.2 and [basic.life]/7 does only disallow non-static member function calls on glvalues referring to the after-lifetime object. Does this imply the call is valid? ( As discussed in the comments I am likely misunderstanding [basic.life]/7 and it probably does apply here.)

如果将析构函数调用a->~A()替换为delete anew(a) A(使用#include<new>),答案会改变吗?

Does the answer change if I replace the destructor call a->~A() with delete a or new(a) A (with #include<new>)?

对我的问题进行一些详尽的编辑和澄清:

Some elaborating edits and clarifications on my question:

如果我将成员函数调用和destructor/delete/placement-new分为两个语句,我认为答案很明确:

If I were to separate the member function call and the destructor/delete/placement-new into two statements, I think the answers are clear:

  1. a->A(); a->f(0):UB,因为a的生存期之外对a进行的非静态成员调用. (不过请参见下面的修改)
  2. delete a; a->f(0):与上述相同
  3. new(a) A; a->f(0):定义明确,请调用新对象
  1. a->A(); a->f(0): UB, because of non-static member call on a outside its lifetime. (see edit below, though)
  2. delete a; a->f(0): same as above
  3. new(a) A; a->f(0): well-defined, call on the new object

但是,在所有这些情况下,a->f都在第一个相应的语句之后排序,而在我的第一个示例中,此顺序是相反的.我的问题是这种逆转是否允许答案改变?

However in all these cases a->f is sequenced after the first respective statement, while this order is reversed in my initial example. My question is whether this reversal does allow for the answers to change?

对于C ++ 17之前的标准,我最初认为这三种情况都会导致未定义的行为,因为a->f的求值取决于a的值,但是相对于该参数的求值而言却是未排序的对a造成副作用.但是,仅当标量值存在实际副作用时,这才是未定义的行为.写入标量对象.但是,没有写入标量对象,因为A是微不足道的,因此,我也将对在C ++ 17之前的标准情况下违反了什么约束也很感兴趣.特别是,现在对我来说不清楚新安置的情况.

For standards before C++17, I initially thought that all three cases cause undefined behavior, already because the evaluation of a->f depends on the value of a, but is unsequenced relative to the evaluation of the argument which causes a side-effect on a. However, this is undefined behavior only if there is an actual side-effect on a scalar value, e.g. writing to a scalar object. However, no scalar object is written to because A is trivial and therefore I would also be interested in what constraint exactly is violated in the case of standards before C++17, as well. In particular, the case with placement-new seems unclear to me now.

我刚刚意识到,有关对象生存期的措辞在C ++ 17和当前草案之间发生了变化.在n4659(C ++ 17草案)中,[basic.life]/1说:

I just realized that the wording about the lifetime of objects changed between C++17 and the current draft. In n4659 (C++17 draft) [basic.life]/1 says:

类型为T的对象o的生存期在以下情况下结束:

The lifetime of an object o of type T ends when:

  • 如果T是一类 如果类型为非平凡的析构函数(15.4),则析构函数调用会开始
  • if T is a class type with a non-trivial destructor (15.4), the destructor call starts

[...]

当前草案说:

类型为T的对象o的生存期在以下情况下结束:

The lifetime of an object o of type T ends when:

[...]

  • 如果T是类类型,则析构函数调用开始,或者

[...]

因此,我想我的示例在C ++ 17中确实具有明确定义的行为,但在当前的(C ++ 20)草案中却没有,因为析构函数调用很简单并且A对象的生存期是' t结束了.我也希望对此进行澄清.对于用delete或placement-new表达式替换析构函数调用的情况,我最初的问题甚至对于C ++ 17仍然有效.

Therefore, I suppose my example does have well-defined behavior in C++17, but not he current (C++20) draft, because the destructor call is trivial and the lifetime of the A object isn't ended. I would appreciate clarification on that as well. My original question does still stands even for C++17 for the case of replacing the destructor call with delete or placement-new expression.

如果f在其主体中访问*this,则在析构函数调用和delete表达式的情况下可能存在未定义的行为,但是在此问题中,我想重点关注调用本身是否有效. 但是请注意,根据呼叫本身是否是未定义的行为,我的问题使用new-place的变体可能不会对f中的成员访问产生影响.但是在那种情况下,可能会有一个后续问题,尤其是对于新放置的情况,因为我不清楚,函数中的this是否会始终自动引用新对象,或者是否可能需要潜在地引用新对象被(取决于A的成员).

If f accesses *this in its body, then there may be undefined behavior for the cases of destructor call and delete expression, however in this question I want to focus on whether the call in itself is valid or not. Note however that the variation of my question with placement-new would potentially not have an issue with member access in f, depending on whether the call itself is undefined behavior or not. But in that case there might be a follow-up question especially for the case of placement-new because it is unclear to me, whether this in the function would then always automatically refer to the new object or whether it might need to potentially be std::laundered (depending on what members A has).

虽然A确实有一个琐碎的析构函数,但更有趣的情况可能是它具有一些副作用,编译器可能会出于优化目的而对其进行假设. (我不知道是否有任何编译器使用这样的东西.)因此,对于A也具有非平凡的析构函数的情况,尤其是在两种情况下答案不同的情况下,我都欢迎给出答案.

While A does have a trivial destructor, the more interesting case is probably where it has some side effect about which the compiler may want to make assumptions for optimization purposes. (I don't know whether any compiler uses something like this.) Therefore, I welcome answers for the case where A has a non-trivial destructor as well, especially if the answer differs between the two cases.

此外,从实际角度看,琐碎的析构函数调用可能不会影响生成的代码,并且(不太可能?)基于未定义行为假设的优化,所有代码示例都极有可能生成可在大多数编译器上按预期运行的代码.我对理论而非实际观点更感兴趣.

Also, from a practical perspective, a trivial destructor call probably does not affect the generated code and (unlikely?) optimizations based on undefined behavior assumptions aside, all code examples will most likely generate code that runs as expected on most compilers. I am more interested in the theoretical, rather than this practical perspective.

该问题旨在更好地理解语言的细节.我不鼓励任何人编写这样的代码.

This question intends to get a better understanding of the details of the language. I do not encourage anyone to write code like that.

推荐答案

的确,琐碎的析构函数什么都不做,甚至没有结束对象的生存期,而不是C ++ 20的计划.因此,问题是,呃,琐碎的,除非我们假设一个非琐碎的析构函数或诸如delete之类的更强的东西.

It’s true that trivial destructors do nothing at all, not even end the lifetime of the object, prior to (the plans for) C++20. So the question is, er, trivial unless we suppose a non-trivial destructor or something stronger like delete.

在这种情况下,C ++ 17的排序无济于事:调用(而非类成员访问)使用指向对象的指针(

In that case, C++17’s ordering doesn’t help: the call (not the class member access) uses a pointer to the object (to initialize this), in violation of the rules for out-of-lifetime pointers.

侧面说明:如果仅未定义一个订单,则C ++ 17之前的未指定订单"也将是:如果

Side note: if just one order were undefined, so would be the "unspecified order" prior to C++17: if any of the possibilities for unspecified behavior are undefined behavior, the behavior is undefined. (How would you tell the well-defined option was chosen? The undefined one could emulate it and then release the nasal demons.)

这篇关于在C ++ 17中的对象生存期之外调用非静态成员函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆