【问题标题】:Will c++ range-based for loop call the destructor of iterator?c++ 基于范围的for循环会调用迭代器的析构函数吗?
【发布时间】:2020-07-19 00:31:24
【问题描述】:

在尝试实现双指针的迭代器时,我发现了一些有趣的事情:

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

说明

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. 我认为当对象aamain() 中的局部变量)被销毁时会打印最后一个地址。既然和_iter的地址一样,那是不是说明_iter的内存已经被释放了呢?那为什么没有调用析构函数呢?
  4. 第三个地址是什么?为什么它与构造函数打印的所有地址不同?为什么在 for 循环结束时调用析构函数?

环境

  • 操作系统:macOS Catalina
  • Apple clang 版本 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;
}

感谢您的阅读!

【问题讨论】:

  • 0x7ffee567a9b0 显然是aa的地址。 0x7ffee567a988 必须是 aa 的副本(您没有实现复制构造函数,因此您看不到它正在构造。
  • 将复制构造函数添加到Iter,您应该会看到更多内容。
  • 并使用 -fno-elide-consteructors 进行编译,以确保您看到复制构造函数正常工作。
  • 看这个:onlinegdb.com/r18W8QYwL 第一个构造函数就是你创建它的时候。第二个副本由 for 循环制作。 for 循环也会破坏副本。然后在 main() 结束时,你所做的那个被破坏了。

标签: c++ for-loop iterator c++17 destructor


【解决方案1】:

基于范围的 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,另一个在脱糖 for 语句中名为 __begin。在aa() 中命名为_iter 的那个在aa 中就地构造。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 2011-10-20
    • 1970-01-01
    • 2015-11-20
    • 1970-01-01
    • 2017-03-20
    • 2014-09-13
    相关资源
    最近更新 更多