基于 c++ 范围的 for 循环会调用迭代器的析构函数吗?



当尝试实现双指针的迭代器时,我发现了一些有趣的东西:

  • 调用析构函数的时间让我感到困惑。
  • 无法理解对象的内存地址。

解释

class A

我有一个名为A的类,它将为整数序列 (_ori_aa) 分配一些内存。

class A {
public:
// constructor and destructor
// ...
Iter<int> aa() const {
Iter<int> _iter;
_iter.set(_aa, _len);
return _iter;
}
private:
const int _len;
int * _ori_aa;  // sequence of numbers: {0, 1, 2, 3}
int ** _aa;     // pointers to _ori_aa: {_ori_aa, _ori_aa+1, ...}
};

struct Iter

还有一个名为Iter的结构,它可以帮助我在A对象中迭代双指针_aa

为了观察,我在构造函数和析构函数中打印自身的内存地址(this)。

该函数meow()也打印内存地址,但它用于手动调用。

template <typename T>
struct Iter {
Iter() { cout << '+' << this << endl; }
~Iter() { cout << '-' << this << endl; }
// ...
void meow() {
cout << '?' << this << endl;
}
// ...
};

main()

在主函数中,

  1. 我创建一个A的对象,然后调用aa(),这将生成一个Iter对象并按值返回。
  2. 我创建了一个Iter对象,并手动调用meow()以查看其地址
  3. 我使用基于范围的 for 循环来打印所有数字。
  4. 我打印一个分隔符来指示循环的结束
int main() {
A a;
Iter<int> aa = a.aa();      // copy by value
aa.meow();
for(const int & n : aa) {
cout << n << endl;
}
cout << "-------" << endl;
}

问题

这是程序的输出:

+0x7ffee567a9b0
?0x7ffee567a9b0
0
1
2
3
-0x7ffee567a988
-------
-0x7ffee567a9b0

我的问题是:

  1. 这些打印的地址对应于哪些操作?
  2. 我知道在aa()中创建_iter时打印了第一个地址,但是什么时候调用析构函数?我以为_iter会在aa()回来后被摧毁,但似乎没有。
  3. 我以为最后一个地址是在对象aa(main()中的局部变量)被销毁时打印的。既然它与_iter的地址相同,是否意味着_iter的记忆已经被释放了?那为什么没有调用析构函数呢?
  4. 第三个地址是什么?为什么它与构造函数打印的所有地址不同?为什么在 for 循环结束时调用析构函数?

环境

  • 操作系统: macOS 卡塔琳娜
  • 苹果叮当版本 11.0.3 (clang-1103.0.32.29)
  • 编译选项:-std=c++17

法典

以下是完整的代码,

#include <iostream>
using namespace std;
template <typename T>
struct Iter {
Iter() { cout << '+' << this << endl; }
~Iter() { cout << '-' << this << endl; }
T ** pp {nullptr};
int len {0};
int it {0};
void meow() {
cout << '?' << this << endl;
}
void set(T ** pi, int l) {
pp = pi;
len = l;
}
Iter & begin() {
it = 0;
return *this;
}
int end() const {
return len;
}
T & operator*() {
return *pp[it];
}
bool operator!=(int rhs) {
return this->it < rhs;
}
Iter & operator++() {
++it;
return *this;
}
};
class A {
public:
A() : _len(4) {
_ori_aa = new int [_len];
_aa = new int * [_len];
for(int i = 0; i < _len; i++) {
_ori_aa[i] = i;
_aa[i] = _ori_aa + i;
}
}
~A() {
delete [] _aa;
delete [] _ori_aa;
}
Iter<int> aa() const {
Iter<int> _iter;
_iter.set(_aa, _len);
return _iter;
}
private:
const int _len;
int * _ori_aa;
int ** _aa;
};
int main() {
A a;
Iter<int> aa = a.aa();      // copy by value
aa.meow();
for(const int & n : aa) {
cout << n << endl;
}
cout << "-------" << endl;
}

感谢您的阅读!

基于范围的 for 语句:

for (const int & n : aa) {
cout << n << endl;
}

只是以下内容的语法糖(请参阅 cppref):

{
auto&& __range = aa;
auto __begin = __range.begin();
auto __end = __range.end();
for (; __begin != __end; ++__begin) {
const int & n = *__begin;
cout << n << endl;
}
}

这应该可以帮助您了解其他Iter在哪里建造以及在哪里被破坏。

请注意,此程序中正好有两个Iter对象:一个名为aa的对象和名为__begin的脱糖 for 语句中的对象。aa()内部名为_iter的那个是在aa年建造的。

最新更新