Определение рекурсивного класса дает неверные указатели в c ++ - PullRequest
1 голос
/ 20 апреля 2020

Я пытаюсь создать класс, который имеет рекурсивную структуру, как кукла Матриоска, чтобы быть более понятным. Например, класс A содержит 8 детей типа A. Я использую указатели, как указано в этом вопросе: Может ли класс c ++ включать себя в качестве члена?

И я назначаю значение индекса в каждом экземпляре класса, чтобы отслеживать, насколько класс находится ниже родительского.

Это мой код:

Tester.h

#pragma once

const unsigned int MAX_RECURSION = 3;

struct Tester
{

    Tester* Children[2][2][2];
    unsigned int Index;

    Tester(unsigned int index) : Index(index), Children()
    {
        std::cout << std::string(index, '-') << "(" << index << ")" << std::endl;
        if (index < MAX_RECURSION) {
            for (int x = 0; x < 2; x++) {
                for (int y = 0; y < 2; y++) {
                    for (int z = 0; z < 2; z++) {

                        this->Children[x][y][z] = &Tester(index + 1);;
                    }
                }
            }
        }

    }

    void Walk(int& tr)
    {

        if (this->Index < MAX_RECURSION) {
            for (int x = 0; x < 2; x++) {
                for (int y = 0; y < 2; y++) {
                    for (int z = 0; z < 2; z++) {
                        tr++;
                        (this->Children[x][y][z])->Walk(tr);
                    }
                }
            }

        }
    }

};

Main. cpp:

#include "Tester.h"
#include <iostream>
int main() {

    int c = 0;

    Tester t(1);
    t.Walk(c);

    return 0;
}

Значение c содержит количество выполнений функции Tester::Walk.

При запуске этого кода все классы создаются правильно, как вы можете видеть в выходном журнале:

-(1)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
--(2)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)
---(3)

Класс с индексом 3 действительно был создан 8 * 8 раз, поэтому 64. (8 для каждой рекурсии).

Но когда я пытаюсь Walk уменьшить значение Children в t, функция ходьбы запускается только 8 раз. Отладкой позже я обнаружил, что только первые 8 классов Tester (первые внутри класса t в main. cpp) имеют правильный индекс, а остальные имеют индекс 3435973836. Сначала я думал, что у рекурсивного создания были проблемы, но журнал показывает, что индексы работают правильно (один печатается 1 раз, 2 8 раз и 3 64 раза).

Что вызывает такое поведение?

1 Ответ

2 голосов
/ 20 апреля 2020

Не храните указатели на временные объекты.

Их Срок службы слишком короткий. Компилятор может предупредить вас об этом, если не сделает этого прямо из-за ошибки компилятора. Например. g ++ 9.3 испускает

..\src\test.cpp:18:68: error: taking address of rvalue [-fpermissive]
   18 |                         this->Children[x][y][z] = &Tester(index + 1);;
      |                                                                    ^

In

this->Children[x][y][z] = &Tester(index + 1);;

Tester(index + 1) создает неназванный временный объект, который живет только до конца строки. Прежде чем вы сможете использовать указатель на этот объект, он вышел из области видимости и был уничтожен.

Самое прямое решение этой проблемы - динамическое выделение

this->Children[x][y][z] = new Tester(index + 1);
* вручную. 1016 *, но теперь вы должны вручную управлять временем жизни всех выделенных Tester с, и это может быть намного сложнее, чем кажется.

Рекомендуется использовать интеллектуальный указатель для управления время жизни Tester с. Я буду использовать a std::unique_ptr, потому что это самый простой и наиболее ограниченный интеллектуальный указатель:

#include <iostream>
#include <memory> // unique_ptr, make_unique
const unsigned int MAX_RECURSION = 3;

struct Tester
{

    std::unique_ptr<Tester> Children[2][2][2]; 
        // array of Testers with scope-managed lifetime. When the array goes out of 
        // scope, all of the Testers will be automatically destroyed.
    unsigned int Index;

    Tester(unsigned int index) :  Children(), Index(index)
    {
        std::cout << std::string(index, '-') << "(" << index << ")" << std::endl;
        if (index < MAX_RECURSION) {
            for (int x = 0; x < 2; x++) {
                for (int y = 0; y < 2; y++) {
                    for (int z = 0; z < 2; z++) {

                        this->Children[x][y][z] = std::make_unique<Tester>(index + 1);
                            // makes a unique_ptr referencing a brand-new Tester
                    }
                }
            }
        }

    }

    void Walk(int& tr)
    {

        if (this->Index < MAX_RECURSION) {
            for (int x = 0; x < 2; x++) {
                for (int y = 0; y < 2; y++) {
                    for (int z = 0; z < 2; z++) {
                        tr++;
                        (this->Children[x][y][z])->Walk(tr);
                    }
                }
            }

        }
    }
};
...