Будет ли C ++ на основе диапазона для l oop вызывать деструктор итератора? - PullRequest
3 голосов
/ 07 апреля 2020

При попытке реализовать итератор двойного указателя я обнаружил кое-что интересное:

  • Время вызова деструктора сбивает меня с толку.
  • Невозможно понять память адреса объектов.

Объяснение

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, которая может помочь мне перебрать двойной указатель _aa в объекте A.

Для наблюдения я печатаю адрес своей памяти (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. Я создаю o наберите Iter и наберите meow() вручную на , см. его адрес .
  3. Я использую на основе диапазона для l oop, чтобы напечатать все числа.
  4. Я печатаю разделитель, чтобы указать конец l oop.
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. Я знаю, что первый адрес печатается, когда _iter создается в aa(), но когда вызывается деструктор? Я думал, что _iter будет уничтожено сразу после возврата aa(), хотя это не так.
  3. Я думал, что последний адрес печатается, когда объект aa (локальная переменная в main() ) уничтожен. Поскольку он совпадает с адресом _iter, означает ли это, что память _iter уже освобождена? Тогда почему деструктор не был вызван?
  4. Какой третий адрес? Почему он отличается от всех адресов, напечатанных конструктором? Почему деструктор вызывается в конце для l oop?

Environment

  • ОС: 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;
}

Спасибо за чтение!

1 Ответ

0 голосов
/ 07 апреля 2020

Диапазон для оператора:

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. Тот, который называется _iter внутри aa(), построен на месте в aa.

...