【问题标题】:lifetime of automatic storage that enters a nested scope进入嵌套范围的自动存储的生命周期
【发布时间】:2021-01-03 06:04:03
【问题描述】:

假设有一个对象具有自动存储持续时间,并且该对象是从嵌套范围内复制初始化的,例如循环体。从嵌套范围内创建的值的生命周期是否扩展到包含范围?

#include<iostream>
using namespace std;

class Thing {
public:
    int data;
    Thing(int data) : data(data) { cout << "making a thing" << endl; }
    ~Thing() { cout << "destroying a thing" << endl; }
};

int main() {
    Thing t = Thing(-1);
    for (int i = 0; i < 4; i++) {
        t = Thing(i); // this is both created AND destroyed from within this scope...?
    }
    cout << t.data << endl; // undefined behavior?
}

现在,最后访问t.data 是可行的,但我看到每个Thing 的析构函数在每次循环迭代时都会调用一次,所以我可能只是走运了?

这看起来很相关(但我不是律师,所以很难破译):some 2011 c++ standard
具体来说:

  1. 对于这种不具有可变长度数组类型的 [具有自动存储持续时间] 的对象,其生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束......李>

因此,如果我的代码 sn-p 是未定义的行为 - 对于循环体更改局部变量,该局部变量应该手动堆分配,然后手动释放,或者...?

【问题讨论】:

  • t 总是指的是同一个Thing。表达式t = Thing(i); 不会改变t 对象所引用的which Thing。它只是创建一个临时的新Thing,将该值复制到名为tThing,然后销毁临时Thing。这是价值语义,可能会让具有 C# 和 Java 等其他语言背景的开发人员感到惊讶。
  • 不要阅读典型问题的标准,这确实非常困难(显然)。 cppreference 是参考的最佳默认选择。

标签: c++


【解决方案1】:

这是完全合法的、已定义的行为。

您的错误在于认为在块内创建的Thing 的生命周期很重要。它没有。复制之后,分配给复制的t 并没有改变它的;它仍然是在块外创建的t,它只是更新了它的值。它的寿命完全不受影响。在您引用的上下文中,“与它[t] 关联的块”是顶级函数范围(声明它的位置),而不是复制分配它的块。

在块内创建的Thing 每次都会过期,但这没关系;它被复制了,然后再也没有使用过。

【讨论】:

  • 回想起来很明显。我打印了所有内容的地址(尤其是在复制初始化期间),您所说的是合法的。感谢您的宝贵时间。
【解决方案2】:

这里没有未定义的行为:

#include<iostream>

class Thing {
public:
    int data;
    Thing(int data) : data(data) { std::cout << "making a thing" << data << std::endl; }
    ~Thing() { std::cout << "destroying a thing" << data << std::endl; }

    Thing& operator=(const Thing &t) { data = t.data; std::cout << "operator=" << std::endl; return *this; }
};
int main() {
    Thing t = Thing(-1); // create object t
    for (int i = 0; i < 4; i++) {
        t = Thing(i);
        // 1. create an object Thing(i)
        // 2. use operator= to copy that object into the object t
        // 3. Thing(i) object gets destroyed at the end of scope
    }
    // object t is still valid, but it was modified in the loop with operator=
    cout << t.data << endl; // no undefined behavior here
}

您可以在 Thing 类中添加复制赋值运算符 (operator=) 成员函数来检查这一点。在您的情况下,编译器根据标准中的规则隐式添加了此运算符:

如果没有为类提供用户定义的复制赋值运算符 类型(结构、类或联合),编译器将始终声明一个 作为类的内联公共成员。这个隐式声明的副本 赋值运算符的形式为 T& T::operator=(const T&) 如果所有 以下是正确的:

  1. T 的每个直接基 B 都有一个复制赋值运算符,其 参数是 B 或 const B& 或 const volatile B&
  2. 每个非静态数据 类类型的 T 的成员 M 或类类型的数组有一个副本 参数为 M 或 const M& 或 const 的赋值运算符 不稳定的并购

【讨论】:

  • 更少侵入性和更准确的演示:godbolt.org/z/6esqT6(您的修改会稍微改变类的行为:禁用移动分配)。
  • 是的,完全同意,它还包括移动任务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-13
  • 2012-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多