Возвращает структуру, созданную из новой структуры внутри функции

В этом коде указатель массива в структуре, возвращаемой функцией, указывает на тот же блок памяти, который был определен с новой структурой?

#include <iostream>
#include <math.h>

struct Arr
{
    const int Col;
    const int Row;
    double* CAR{ new double[Col * Row] };
    Arr(int y, int x) : Col(x), Row(y) {}
    void Del() { delete[] CAR; }
    int Indx(int y, int x) { return (x + y * Col); }
    int Size() { return Col * Row; }
    void Print(std::string title);
};
void Arr::Print(std::string title)
{
    std::cout << title << '\n';
    for (int I = 0; I < Row; I++)
    {
        for (int In = 0; In < Col; In++)
        {
            std::cout << CAR[Indx(I, In)] << " / ";
        }
        std::cout << '\n';
    }
}
const Arr retfunc(std::string h, Arr& a, Arr& b)
{
    Arr* temp = NULL;
    if (h == "Had") 
    {
        temp = new Arr(a.Row, a.Col);
        for (int I = 0; I < a.Row; I++)
        {
            for (int In = 0; In < a.Col; In++)
            {
                temp->CAR[temp->Indx(I, In)] = a.CAR[a.Indx(I, In)] * b.CAR[b.Indx(I, In)];
            }
        }
    } Arr T = *temp; return T;
}

int main()
{
    int val = 5;
    Arr a(2, 2);
    Arr b(2, 2);
    for (int I = 0; I < 2; I++)
    {
        for (int In = 0; In < 2; In++)
        {
            a.CAR[a.Indx(I, In)] = 10.0 / val + In;
            b.CAR[b.Indx(I, In)] = 8.0 / val + In;
        }
    }
    a.Print("a");
    b.Print("b");
    Arr S = retfunc("Had", a, b);
    S.Print("S");   
    S.Del();
}

Таким образом, по сути, вызов удаления на S очищает ту же память, которая была выделена в retfunc?


person Yugenswitch    schedule 15.03.2021    source источник
comment
Когда вы делаете double* CAR{ new double[Col * Row] };, каковы значения Col и Row? Подсказка: их значения еще не установлены...   -  person Some programmer dude    schedule 15.03.2021
comment
Они устанавливаются в конструкторе, если только я не делаю это правильно?   -  person Yugenswitch    schedule 15.03.2021
comment
Они устанавливаются в конструкторе, но слишком поздно!   -  person Damien    schedule 15.03.2021
comment
@Yugenswitch Это происходит после инициализации CAR.   -  person molbdnilo    schedule 15.03.2021
comment
Нет причин использовать new в retfunc. (И вы также пропускаете этот объект и, возможно, разыменовываете нулевой указатель.)   -  person molbdnilo    schedule 15.03.2021
comment
поэтому я должен переместить конструктор выше?   -  person Yugenswitch    schedule 15.03.2021
comment
Вы должны реализовать new в конструкторе.   -  person Damien    schedule 15.03.2021
comment
Просто поместите new в список инициализаторов конструктора. Или, что еще лучше, используйте вместо этого std::vector<double>.   -  person Some programmer dude    schedule 15.03.2021
comment
функция обычно имеет много различных матричных операций, которые могут быть определены строкой, некоторые операции производят разные размеры. вот почему я использую новые. это неправильно?   -  person Yugenswitch    schedule 15.03.2021
comment
Ясно, значит, когда я назначаю *temp для T, CAR не создает запрос на дополнительную память?   -  person Yugenswitch    schedule 15.03.2021
comment
@Damien Они устанавливаются в конструкторе, но слишком поздно! Я так не думаю (но не совсем уверен). Поэтому я попробовал в g++ 10.2: Демонстрация в coliru . В соответствии с этим кажется, что работает переопределение элементов, инициализированных по умолчанию, в конструкторе, и они по-прежнему будут учитываться при инициализации по умолчанию других членов. (Допускаю, что демо еще не доказательство...)   -  person Scheff's Cat    schedule 15.03.2021
comment
@Someprogrammerdude: я тестировал этот код в MSVC и режиме отладки и был поражен тем, что MSVC обрабатывает его правильно: он назначает члены в порядке объявления, поэтому Col и Row действительно инициализируются перед использованием для инициализации CAR. Код по-прежнему является рецептом для утечек памяти, но инициализация кажется правильной, по крайней мере, для MSVC (не тестировалось с другими компиляторами).   -  person Serge Ballesta    schedule 15.03.2021
comment
@Scheff и Серж, я чувствую, что возникает вопрос языкового юриста ... :)   -  person Some programmer dude    schedule 15.03.2021
comment
@Someprogrammerdude Я не буду добавлять тег к этому вопросу :-)   -  person Serge Ballesta    schedule 15.03.2021
comment
@Scheff: С constexpr вы гарантируете отсутствие UB, так что Demo является доказательством ( если не ошибка компилятора ;-)).   -  person Jarod42    schedule 15.03.2021
comment
@Scheff Действительно интересно. Я могу ошибаться с точки зрения юриста. Но лично я бы избегал такой конструкции... :)   -  person Damien    schedule 15.03.2021


Ответы (2)


Вы должны просто иметь return *temp в конце retfunc, который объявлен для возврата объекта Arr. Как только это будет исправлено, S.Del() правильно освободит блок памяти, выделенный в retfunc.

Но это ужасный код. Вы не можете освободить блоки, выделенные в объектах a и b. Не ошибка, потому что конец программы освободит их, но это рецепт утечек памяти. Кроме того, у вас есть выделенный вручную блок памяти в конструкторе, но нет явного конструктора копирования или оператора присваивания. Если вы пишете:

Arr c = a;

и c, и a будут использовать один и тот же массив CAR, который может быть или не быть тем, что вы хотите.

Короче говоря, я думаю, что это не вопиющая ошибка, а как минимум антипаттерн.

person Serge Ballesta    schedule 15.03.2021

Во-первых, функция retfunc имеет утечку памяти, потому что объект temp, динамически выделенный внутри функции, не освобождается.

Поскольку у класса нет явного конструктора копирования, то в этом объявлении

Arr T = *temp

используется поэлементное копирование элементов данных объекта temp в объект T. Значение указателя CAR объекта temp будет скопировано в указатель CAR объекта T и будет действительным указателем из-за срока службы -время динамически размещаемого объекта temp.

Когда внутри класса есть динамически выделенная память, вам нужно написать явный деструктор, копировать конструкторы для lvalue и rvalue и копировать операторы присваивания для lvalue и rvalue.

person Vlad from Moscow    schedule 15.03.2021
comment
это то, что я думал, но я подумал, может быть, потому что CAR - это указатель, когда указатель из temp копируется в S, а затем из S в T, не означает ли это, что он просто копирует адрес памяти? - person Yugenswitch; 15.03.2021
comment
@Yugenswitch верно, указатель копируется. А как же сам объект temp? - person user253751; 15.03.2021
comment
ах здорово! так что, если я просто удалю temp, утечки памяти не будет? - person Yugenswitch; 15.03.2021
comment
Если мои тесты верны, основная проблема заключается в отсутствии return *temp в конце retfunc (помимо того, что код является рецептом для утечек памяти, поскольку выделенный блок не освобождается в dtor, и правила 5) - person Serge Ballesta; 15.03.2021
comment
вклад очень ценится! еще очень нуб в указателях. - person Yugenswitch; 15.03.2021
comment
@Yugenswitch: если вы новичок, избегайте ручного выделения в ctror или соблюдайте правило 5, чтобы автоматически освобождать его в деструкторе и обрабатывать его в операциях копирования или перемещения. Есть крайние случаи, поэтому, если вы пойдете по этому пути, покажите свой первый рабочий код на CodeReview. - person Serge Ballesta; 15.03.2021