Will c++ range-based for loop call the destructor of iterator?

Viewed 373

When trying to implement an iterator of a double-pointer, I found something interesting:

  • The timing of the destructor being called confuses me.
  • Unable to understand the memory addresses of the objects.

Explanation

class A

I have a class called A, which will allocate some memory for a sequence of integers ( _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

And a struct called Iter, which can help me to iterate the double-pointer _aa in an A object.

For observation, I print the memory address of itself ( this ) in the constructor and destructor.

The function meow() also prints the memory address, but it is used for a manual call.

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

main()

In the main function,

  1. I create an object of A and then call aa(), which will generate an Iter object and return by value.
  2. I create an object of Iter and call meow() manually to see its address.
  3. I use range-based for loop to print all the numbers.
  4. I print a divider to indicate the end of the loop.
int main() {
    A a;
    Iter<int> aa = a.aa();      // copy by value
    aa.meow();
    for(const int & n : aa) {
        cout << n << endl;
    }
    cout << "-------" << endl;
}

Problem

This is the output of the program:

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

My questions are:

  1. What operations do these printed addresses correspond to?
  2. I know the first address is printed when _iter is created in aa(), but when does the destructor be called? I thought _iter will be destroyed just after aa() return, while it didn't seem to do.
  3. I thought the last address is printed when the object aa ( local variable in main() ) is destroyed. Since it is the same as the address of _iter, does it mean the memory of _iter had already been freed? Then why the destructor didn't be called?
  4. What is the third address? Why it is different from all the addresses printed by the constructor? Why a destructor is called at the end of the for loop?

Environment

  • OS: macOS Catalina
  • Apple clang version 11.0.3 (clang-1103.0.32.29)
  • Compilation options: -std=c++17

Code

The following is the complete code,

#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;
}

Thank you for your reading!

1 Answers

A range-based for statement:

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

Is just syntax sugar for the following (see cppref):

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

Which should help you understand both where your other Iter is being constructed and where it is being destroyed.

Note that there are exactly two Iter objects being constructed in this program: the one named aa and the one in the desugared for statement named __begin. The one named _iter inside of aa() is constructed in place in aa.

Related