Будет ли приведенный ниже код вызывать утечку памяти в C++

class someclass {};

class base
{
    int a;
    int *pint;
    someclass objsomeclass;
    someclass* psomeclass;
public:
    base()
    {
        objsomeclass = someclass();
        psomeclass = new someclass();
        pint = new int(); 
        throw "constructor failed";
        a = 43;
    }
}

int main()
{
    base temp();
}

В приведенном выше коде конструктор выдает. Какие объекты будут утечками и как избежать утечек памяти?

int main()
{
    base *temp = new base();
}

Как насчет приведенного выше кода? Как можно избежать утечки памяти после того, как конструктор выбросит?

Я знаю, у меня ужасный характер, и я не могу не придираться к мелочам. Я ничего не могу поделать. Мои 2 цента: инструкция objsomeclass = someclass (); не нужно. В теле конструктора objsomeclass уже инициализирован по умолчанию. objsomeclass (someclass ()) ниже тоже не имеет смысла.

Maciej Hehl 29.09.2008 12:36

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

yesraaj 29.09.2008 13:21

Да, я знаю, это просто пример. Вот почему я назвал это придиркой. Кстати, конструктор base () может быть общедоступным :)

Maciej Hehl 29.09.2008 14:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
18
3
12 032
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Все, что вы «новое», необходимо удалить, иначе вы вызовете утечку памяти. Итак, эти две строки:

psomeclass = new someclass();
pint = new int(); 

Вызовет утечку памяти, потому что вам нужно сделать:

delete pint;
delete psomeclass;

В блоке finally, чтобы избежать их утечки.

Также эта строка:

base temp = base();

Не нужно. Вам просто нужно сделать:

base temp;

Добавлять «= base ()» не нужно.

В C++ нет такого понятия, как блок «finally».

John Millikin 29.09.2008 09:00

Правда, вы можете иметь или не иметь к нему доступ в зависимости от вашего вкуса C++ - в противном случае вам нужно будет убедиться, что выделения удаляются независимо от выбранного пути кода.

Colen 29.09.2008 09:02

Ваше замечание о дополнительной инициализации неверно. Результирующий объект будет инициализирован только один раз и не будет скопирован.

John Millikin 29.09.2008 09:06

Да, этот код приведет к утечке памяти. Блоки памяти, выделенные с помощью «нового», не освобождаются при возникновении исключения. Это часть мотивации RAII.

Чтобы избежать утечки памяти, попробуйте что-то вроде этого:

psomeclass = NULL;
pint = NULL;
/* So on for any pointers you allocate */

try {
    objsomeclass = someclass();
    psomeclass = new someclass();
    pint = new int(); 
    throw "constructor failed";
    a = 43;
 }
 catch (...)
 {
     delete psomeclass;
     delete pint;
     throw;
 }

вместо использования указателей с помощью объекта (интеллектуального указателя) ситуация улучшится? Поскольку, когда в блоке возникает исключение, автоматические объекты очищаются.

yesraaj 29.09.2008 09:18

умные указатели лучше. Также замените «поднять»; с 'бросить;' Чтобы повторно выбросить текущее исключение.

Martin York 29.09.2008 09:33

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

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

John Millikin 29.09.2008 09:08

Неперехваченное исключение в деструкторе, созданном во время обработки исключений, вызывает вызов std :: terminate (), который по умолчанию вызывает std :: abort (). Поведение по умолчанию можно изменить.

KTC 29.09.2008 09:16

даже несмотря на то, что поведение по умолчанию можно переопределить, ваша версия по-прежнему не может вернуться в приложение, она все равно должна выйти.

Greg Rogers 29.09.2008 09:43

Обе новинки будут протекать.

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

class base {
    int a;
    boost::shared_ptr<int> pint;
    someclass objsomeclass;
    boost::shared_ptr<someclass> psomeclass;

    base() :
        objsomeclass( someclass() ),
        boost::shared_ptr<someclass> psomeclass( new someclass() ),
        boost::shared_ptr<int> pint( new int() )
    {
        throw "constructor failed";
        a = 43;
    }
};

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

int main(){
    base *temp = new base();
}

Для обычного выделения памяти с использованием (не plcaement) new память, выделенная оператором new, освобождается автоматически, если конструктор генерирует исключение. Что касается того, зачем беспокоиться об освобождении отдельных членов (в ответ на комментарии к ответу Майка Б.), автоматическое освобождение применяется только тогда, когда в конструкторе нового объекта создается исключение, а не в других случаях. Кроме того, освобождаемая память - это память, выделенная для членов объекта, а не любая память, которую вы могли выделить, скажем, внутри конструктора. т.е. он освободит память для переменных-членов а, пинта, objsomeclass и псомекласс, но не память, выделенную из новый someclass () и новый int ().

shared_ptr <> будет излишним, если вы владеете объектом и никогда не собираетесь отказываться от совместного владения. Упростите с помощью std :: auto_ptr <>

Martin York 29.09.2008 09:35

// Изменен вопрос, чтобы иметь базу * temp = new base ();

yesraaj 29.09.2008 09:36

И boost :: scoped_ptr <> может быть даже лучше, чем auto_ptr <>, у которого есть собственная банка червей.

Michael Burr 29.09.2008 09:44

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

KTC 29.09.2008 11:09
Ответ принят как подходящий

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

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

Если вы используете что-то вроде шаблона Boost scoped_ptr <>, ваш класс мог бы выглядеть примерно так:

class base{
    int a;
    scoped_ptr<int> pint;
    someclass objsomeclass;
    scoped_ptr<someclass> psomeclass;
    base() : 
       pint( new int),
       objsomeclass( someclass()),
       psomeclass( new someclass())

    {
        throw "constructor failed";
        a = 43;
    }
}

И у вас не будет утечек памяти (и dtor по умолчанию также очистит распределение динамической памяти).


Подводя итог (и, надеюсь, это также ответит на вопрос о

base* temp = new base();

утверждение):

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

  1. деструктор для создаваемого объекта будет вызван нет.
  2. деструкторы для объектов-членов, содержащихся в этом классе объекта, будут вызываться
  3. память для создаваемого объекта будет освобождена.

Это означает, что если ваш объект владеет ресурсами, у вас есть 2 доступных метода для очистки тех ресурсов, которые могли быть уже получены, когда конструктор выбрасывает:

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

Разве перетаскивание в Boost просто и управление памятью не является довольно глупым?

John Millikin 29.09.2008 09:43

Возможно, но scoped_ptr находится в TR1 и будет в C++ 09, так что это то, что все равно следует изучить. А часть Boost с scoped_ptr - это просто набор заголовков. Наконец, вы можете использовать auto_ptr вместо этого для этого простого примера, но auto_ptr, вероятно, следует избегать.

Michael Burr 29.09.2008 09:48

будет ли вызываться dtor базового класса, даже если у меня один раз? что произойдет с базой ниже линии * temp = new base ();

yesraaj 29.09.2008 09:54

Ничего не случится с "base * temp = new base;" line - исключение приведет к освобождению памяти для попытки выделения нового базового объекта (даже если dtor не будет вызываться).

Michael Burr 29.09.2008 09:57

тогда нужно ли заботиться о том, чтобы освободить память об отдельном члене?

yesraaj 29.09.2008 10:05

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

Michael Burr 29.09.2008 10:24

Примечание: будут вызваны деструкторы ПОЛНОСТЬЮ сконструированных членов.

Martin York 29.09.2008 10:45

@Loki Можете ли вы определить «ПОЛНОСТЬЮ построенный»? Разве все члены, кроме a, не будут созданы во время выполнения строки throw?

Nathan 28.05.2014 01:18

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

Martin York 28.05.2014 02:49

вам нужно удалить psomeclass ... Нет необходимости очищать целое число ...

RWendi

Не могли бы вы уточнить, Дэйв Мур? Это про часть "не нужно убирать целое"? Причина этого в том, что указатель памяти Int не стоит много по сравнению с указателем памяти класса, поэтому я сказал, что его не нужно очищать.

RWendi 30.09.2008 04:30

Они оба протекают; стоимость не проблема. Вопрос был в том, просочилась она или нет. И если этот кусок кода выполняется тысячи или миллионы раз, эта небольшая стоимость складывается. Даже если «стоимость» имеет значение, значение имеет не размер указатель, а размер объекта, на который указывает указатель. Например, это возможно для sizeof (someclass) == sizeof (int). И вы не удаляете указатель - вы удаляете указанную сущность.

Chris Cleeland 06.11.2009 20:53

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

Эта простая программа демонстрирует это.

#include <stdio.h>


class A
{
    int x;

public:
    A(int x) : x(x) { printf("A constructor [%d]\n", x); }
    ~A() { printf("A destructor [%d]\n", x); }
};


class B
{
    A a1;
    A a2;

public:
    B()
    :   a1(3),
        a2(5)
    {
        printf("B constructor\n");
        throw "failed";
    }
    ~B() { printf("B destructor\n"); }
};


int main()
{
    B b;

    return 0;
}

Со следующим выводом (с использованием g ++ 4.5.2):

A constructor [3]
A constructor [5]
B constructor
terminate called after throwing an instance of 'char const*'
Aborted

Если ваш конструктор частично выйдет из строя, вы обязаны с этим разобраться. Хуже того, исключение может быть выброшено из конструктора вашего базового класса! Способ справиться с этими случаями - использовать «блок попытки функции» (но даже в этом случае вы должны тщательно кодировать уничтожение вашего частично инициализированного объекта).

Тогда правильный подход к вашей проблеме будет примерно таким:

#include <stdio.h>


class A
{
    int x;

public:
    A(int x) : x(x) { printf("A constructor [%d]\n", x); }
    ~A() { printf("A destructor [%d]\n", x); }
};


class B
{
    A * a1;
    A * a2;

public:
    B()
    try  // <--- Notice this change
    :   a1(NULL),
        a2(NULL)
    {
        printf("B constructor\n");
        a1 = new A(3);
        throw "fail";
        a2 = new A(5);
    }
    catch ( ... ) {   // <--- Notice this change
        printf("B Cleanup\n");
        delete a2;  // It's ok if it's NULL.
        delete a1;  // It's ok if it's NULL.
    }

    ~B() { printf("B destructor\n"); }
};


int main()
{
    B b;

    return 0;
}

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

B constructor
A constructor [3]
B Cleanup
A destructor [3]
terminate called after throwing an instance of 'char const*'
Aborted

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

class C
{
    std::shared_ptr<someclass> a1;
    std::shared_ptr<someclass> a2;

public:
    C()
    {
        std::shared_ptr<someclass> new_a1(new someclass());
        std::shared_ptr<someclass> new_a2(new someclass());

        // You will reach here only if both allocations succeeded. Exception will free them both since they were allocated as automatic variables on the stack.
        a1 = new_a1;
        a2 = new_a2;
    }
}

Удачи, Цви.

Исключение в вашем первом примере не перехвачено, поэтому не происходит раскручивания стека и не вызываются деструкторы. Если вы заключите B b; в try catch, деструкторы будут вызваны должным образом.

M. Dudley 05.02.2013 20:19

Другие вопросы по теме