Почему использование «новых» вызывает утечку памяти?

Сначала я изучил C #, и теперь я начинаю с C ++. Насколько я понимаю, оператор new в C ++ не похож на оператор в C #.

Можете ли вы объяснить причину утечки памяти в этом примере кода?

 class A { ... }; struct B { ... }; A *object1 = new A(); B object2 = *(new B()); 

    Что происходит

    Когда вы пишете T t; вы создаете объект типа T с автоматическим временем хранения . Он будет автоматически очищаться, когда он выходит за frameworks.

    Когда вы пишете new T() вы создаете объект типа T с динамической продолжительностью хранения . Он не будет автоматически очищаться.

    новый без очистки

    Вам нужно передать указатель на него, чтобы delete , чтобы очистить его:

    новичка с удалением

    Однако ваш второй пример хуже: вы разыгрываете указатель и делаете копию объекта. Таким образом вы теряете указатель на объект, созданный с помощью new , поэтому вы никогда не сможете удалить его, даже если хотите!

    новичка с дереф

    Что ты должен делать

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

     A a; // a new object of type A B b; // a new object of type B 

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

     template  class automatic_pointer { public: automatic_pointer(T* pointer) : pointer(pointer) {} // destructor: gets called upon cleanup // in this case, we want to use delete ~automatic_pointer() { delete pointer; } // emulate pointers! // with this we can write *p T& operator*() const { return *pointer; } // and with this we can write p->f() T* operator->() const { return pointer; } private: T* pointer; // for this example, I'll just forbid copies // a smarter class could deal with this some other way automatic_pointer(automatic_pointer const&); automatic_pointer& operator=(automatic_pointer const&); }; automatic_pointer a(new A()); // acts like a pointer, but deletes automatically automatic_pointer b(new B()); // acts like a pointer, but deletes automatically 

    newing with automatic_pointer

    Это распространенная идиома, которая сопровождается не очень описательным именем RAII ( Инициализация ресурсов ). Когда вы приобретаете ресурс, который нуждается в очистке, вы привязываете его к объекту с автоматическим хранением, поэтому вам не нужно беспокоиться о его очистке. Это относится к любому ресурсу, будь то память, открытые файлы, сетевые подключения или все, что вам нравится.

    Эта вещь auto_pointer уже существует в различных формах, я просто предоставил ее, чтобы привести пример. Очень похожий class существует в стандартной библиотеке, называемой std::unique_ptr .

    Также есть старый (pre-C ++ 11) с именем auto_ptr но теперь он устарел, потому что у него странное поведение при копировании.

    И тогда есть некоторые даже более умные примеры, такие как std::shared_ptr , которые позволяют нескольким указателям на один и тот же объект и только очищают его, когда последний указатель уничтожен.

    Пошаговое объяснение:

     // creates a new object on the heap: new B() // dereferences the object *(new B()) // calls the copy constructor of B on the object B object2 = *(new B()); 

    Поэтому к концу этого объекта у вас есть объект в куче, без указателя на него, поэтому его невозможно удалить.

    Другой образец:

     A *object1 = new A(); 

    это утечка памяти, только если вы забыли delete выделенную память:

     delete object1; 

    В C ++ есть объекты с автоматическим хранилищем, созданные в стеке, которые автоматически удаляются, и объекты с динамическим хранилищем в куче, которые вы назначаете new и должны освобождаться от delete . (это все грубо говоря)

    Подумайте, что вы должны иметь delete для каждого объекта, выделенного new .

    РЕДАКТИРОВАТЬ

    Подумайте об этом, object2 не должен быть утечкой памяти.

    Следующий код – это просто мысль, это плохая идея, не нравится такой код:

     class B { public: B() {}; //default constructor B(const B& other) //copy constructor, this will be called //on the line B object2 = *(new B()) { delete &other; } } 

    В этом случае, поскольку other передается по ссылке, это будет точный объект, на который указывает new B() . Таким образом, получение своего адреса by &other и удаление указателя освободит память.

    Но я не могу это подчеркнуть, не делайте этого. Здесь нужно только сказать.

    Учитывая два «объекта»:

     obj a; obj b; 

    Они не будут занимать одно и то же место в памяти. Другими словами, &a != &b

    Присвоение значения одному другому не изменит их местоположение, но оно изменит их содержимое:

     obj a; obj b = a; //a == b, but &a != &b 

    Интуитивно указательные «объекты» работают одинаково:

     obj *a; obj *b = a; //a == b, but &a != &b 

    Теперь давайте посмотрим на ваш пример:

     A *object1 = new A(); 

    Это присваивает значение new A() object1 . Значение – это указатель, означающий object1 == new A() , но &object1 != &(new A()) . (Обратите внимание, что этот пример является недопустимым кодом, это только для объяснения)

    Поскольку значение указателя сохраняется, мы можем освободить память, на которую он указывает: delete object1; Из-за нашего правила это ведет себя так же, как delete (new A()); который не имеет утечки.


    Во втором примере вы копируете объект с указателем. Значение – это содержимое этого объекта, а не фактический указатель. Как и в любом другом случае, &object2 != &*(new A()) .

     B object2 = *(new B()); 

    Мы потеряли указатель на выделенную память, и поэтому мы не можем ее освободить. delete &object2; может показаться, что это сработает, но поскольку &object2 != &*(new A()) , он не эквивалентен delete (new A()) и поэтому недействителен.

    В C # и Java вы используете new для создания экземпляра любого classа, а затем вам не нужно беспокоиться об уничтожении его позже.

    C ++ также имеет ключевое слово «новый», которое создает объект, но в отличие от Java или C #, это не единственный способ создать объект.

    C ++ имеет два механизма для создания объекта:

    • автоматический
    • динамический

    С автоматическим созданием вы создаете объект в области видимости: – в функции или – как член classа (или структуры).

    В функции вы создадите ее так:

     int func() { A a; B b( 1, 2 ); } 

    Внутри classа вы обычно создаете его следующим образом:

     class A { B b; public: A(); }; A::A() : b( 1, 2 ) { } 

    В первом случае объекты автоматически уничтожаются при выходе из блока области видимости. Это может быть функция или блок областей внутри функции.

    В последнем случае объект b уничтожается вместе с экземпляром A, в котором он является членом.

    Объекты выделяются новыми, когда вам нужно управлять временем жизни объекта, а затем для его уничтожения требуется удаление. С помощью технологии, известной как RAII, вы заботитесь об удалении объекта в том месте, где вы его создаете, помещая его в автоматический объект и ожидая, что деструктор этого автоматического объекта вступит в силу.

    Одним из таких объектов является shared_ptr, который будет вызывать логику «deleter», но только тогда, когда уничтожаются все экземпляры shared_ptr, которые делят объект.

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

    Ваши деструкторы также никогда не должны бросать исключения.

    Если вы это сделаете, у вас будет мало утечек памяти.

     B object2 = *(new B()); 

    Эта линия является причиной утечки. Давайте немного разобрать это.

    object2 – это переменная типа B, хранящаяся по адресу 1 (да, я выбираю здесь произвольные номера). Справа вы попросили новый B или указатель на объект типа B. Программа с радостью дает это вам и назначает ваш новый B на адрес 2, а также создает указатель в адресе 3. Теперь, единственный способ доступа к данным в адресе 2 – через указатель в адресе 3. Затем вы разыменовали указатель, используя * чтобы получить данные, на которые указывает указатель (данные в адресе 2). Это эффективно создает копию этих данных и присваивает ее объекту2, указанному в адресе 1. Помните, что это COPY, а не оригинал.

    Теперь вот проблема:

    Вы никогда не храните этот указатель в любом месте, где сможете его использовать! По завершении этого назначения указатель (память в адресе 3, который вы использовали для доступа к адресу2) выходит за frameworks и недоступен! Вы больше не можете вызывать удаление на нем и, следовательно, не можете очистить память в адресе2. То, что у вас осталось, – это копия данных с адреса2 в address1. Две вещи, которые сидят в памяти. Один из них вы можете получить, другой вы не можете (потому что вы потеряли путь к нему). Вот почему это утечка памяти.

    Я бы посоветовал приходить с вашего фона C #, чтобы вы много читали о том, как работают указатели на C ++. Они являются передовой темой и могут занять некоторое время, чтобы понять, но их использование будет бесценным для вас.

    При создании object2 вы создаете копию объекта, созданного с помощью нового, но вы также теряете указатель (никогда не назначенный) (поэтому впоследствии его не удалять). Чтобы этого избежать, вам нужно сделать object2 ссылкой.

    Ну, вы создаете утечку памяти, если вы в какой-то момент не освободите память, которую вы выделили, используя new оператор, передав указатель на эту память оператору delete .

    В ваших двух случаях выше:

     A *object1 = new A(); 

    Здесь вы не используете delete для освобождения памяти, поэтому, если и когда указатель object1 выходит из области видимости, у вас будет утечка памяти, потому что вы потеряете указатель и, следовательно, не сможете использовать оператор delete Это.

    И здесь

     B object2 = *(new B()); 

    вы отбрасываете указатель, возвращаемый new B() , и поэтому никогда не сможете передать этот указатель для delete для освобождаемой памяти. Отсюда другая утечка памяти.

    Именно эта линия сразу же протекает:

     B object2 = *(new B()); 

    Здесь вы создаете новый объект B в куче, а затем создаете копию в стеке. Тот, который был выделен на кучу, больше не может быть доступен и, следовательно, утечка.

    Эта линия не сразу протекает:

     A *object1 = new A(); 

    Будет утечка, если вы никогда не delete d object1 .

    Если это упростит, подумайте о компьютерной памяти как о гостинице, а программы – это клиенты, которые нанимают комнаты, когда они в них нуждаются.

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

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

    Если ваша программа выделяет память и не удаляет ее (она просто перестает ее использовать), тогда компьютер считает, что память все еще используется и не позволит кому-либо использовать ее. Это утечка памяти.

    Это не точная аналогия, но это может помочь.

    Interesting Posts
    Давайте будем гением компьютера.