Подтвердить что ты не робот

Имеет ли этот код на С++ утечку памяти?

struct Foo
{
    Foo(int i)
    {
        ptr = new int(i);
    }
    ~Foo()
    {
        delete ptr;
    }
    int* ptr;
};

int main()
{
    {
        Foo a(8);
        Foo b(7);
        a = b;
    }
    //Do other stuff
}

Если я правильно понимаю, компилятор автоматически создаст функцию члена оператора присваивания для Foo. Однако это просто принимает значение ptr в b и помещает его в a. Первоначально память, выделенная a, кажется потерянной. Я мог сделать вызов a.~Foo(); перед выполнением задания, но где-то я слышал, что вам редко нужно явно вызвать деструктор. Итак, скажем, вместо этого я пишу оператор присваивания для Foo, который удаляет указатель int левого операнда, прежде чем назначать значение r значению l. Например:

Foo& operator=(const Foo& other) 
{
    //To handle self-assignment:
    if (this != &other) {
        delete this->ptr;
        this->ptr = other.ptr;
    }
    return *this;
}

Но если я это сделаю, то, когда Foo a и Foo b выйдут из области видимости, не выполняются оба их деструктора, дважды удалив один и тот же указатель (поскольку оба они указывают на одно и то же сейчас)?

Edit:

Если я правильно понимаю Anders K, это правильный способ сделать это:

Foo& operator=(const Foo& other) 
{
    //To handle self-assignment:
    if (this != &other) {
        delete this->ptr;
        //Clones the int
        this->ptr = new int(*other.ptr);
    }
    return *this;
}

Теперь a клонирует int, на который указывает b, и устанавливает для него свой собственный указатель. Возможно, в этой ситуации delete и new не были необходимы, потому что он просто включает в себя int s, но если элемент данных не был int*, а скорее a Bar* или еще что-то, может потребоваться перераспределение.

Изменить 2: Лучшим решением является идиома копирования и свопинга.

4b9b3361

Ответ 1

Является ли это утечкой памяти?
Нет, это не так.

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

Первоначальный ответ "Нет, он не течет" в этом ответе был неправильным, но решение, которое было и предлагается здесь, является единственным и наиболее подходящим решением проблемы.


Решение ваших проблем:

Не использовать указатель на целочисленный член (int *), но использовать только целое число (int). Здесь вам не нужен динамически выделенный элемент указателя. Вы можете достичь той же функциональности, используя элемент int.
Обратите внимание, что в С++ Вы должны использовать new как можно меньше.

Если по какой-либо причине (которую я не вижу в примере кода), вы не можете обойтись без динамически назначенного элемента указателя, на котором:

Вам нужно следовать Правило трех!


Зачем вам следовать правилу три?

Правило трех:

Если ваш класс нуждается в

a конструктор копирования,
оператор присваивания,
или деструктор,

тогда, вероятно, потребуется все три из них.

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

  • Утечки памяти и
  • Висячие указатели и
  • Потенциал Undefined Поведение двойного освобождения

Что в основном означает, что вы не можете обойтись с неявно сгенерированными версиями, вам нужно предоставить свои перегруженные версии, и это то, о чем говорит правило 3.

Явно предоставленные перегрузки должны сделать глубокую копию выделенного элемента, и таким образом предотвратит все ваши проблемы.

Как правильно реализовать оператор присваивания копирования?

В этом случае наиболее эффективным и оптимизированным способом предоставления оператора присваивания копии является: Идиома с копией и заменой
@Знаменитый ответ GManNickG дает достаточно подробностей, чтобы объяснить преимущества, которые он предоставляет.


Предложение:

Кроме того, вам гораздо лучше использовать умный указатель как член класса, а не необработанный указатель, который обременяет вас явным управлением памятью. Умный указатель будет неявно управлять памятью для вас. Какой умный указатель для использования зависит от семантики жизни и владения, предназначенной для вашего участника, и вам нужно выбрать подходящий умный указатель в соответствии с вашим требование.

Ответ 2

нормальный способ справиться с этим - создать клон объекта, на который указывает указатель, поэтому важно иметь оператор присваивания. когда нет оператора привязки, определено поведение по умолчанию - memcpy, который вызовет сбой, когда оба деструктора попытаются удалить один и тот же объект и утечку памяти, поскольку предыдущее значение, которое ptr указывало на b, не будет удалено.

Foo a

         +-----+
a->ptr-> |     |
         +-----+

Foo b

         +-----+
b->ptr-> |     |
         +-----+

a = b

         +-----+
         |     |
         +-----+
a->ptr            
       \ +-----+
b->ptr   |     |
         +-----+

when a and b go out of scope delete will be called twice on the same object.

отредактируйте: как правильно указал Бенджамин/Альс, вышеизложенное относится только к этому конкретному примеру, см. ниже в комментариях

Ответ 3

Представленный код имеет Undefined Поведение. Таким образом, если он утечки памяти (как и ожидалось), то это всего лишь одно возможное проявление UB. Он также может отправить сердитое угрожающее письмо Бараку Обаме или извергнуть красных (или оранжевых) носовых демонов или ничего не делать или действовать так, как будто не было утечки памяти, чудесным образом восстанавливая память или что-то еще.

Решение: вместо int* используйте int, т.е.

struct Foo
{
    Foo(int i): blah( i ) {}
    int blah;
};

int main()
{
    {
        Foo a(8);
        Foo b(7);
        a = b;
    }
    //Do other stuff
}

Это более безопасно, короче, намного эффективнее и намного понятнее.

Никакое другое решение, представленное для этого вопроса, не превосходит его по любой объективной мере.