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();
}
Как насчет приведенного выше кода? Как можно избежать утечки памяти после того, как конструктор выбросит?
Я согласен, но думаю, что у someclass есть явный конструктор. И я хотел сосредоточиться на том, что объект создается в конструкторе
Да, я знаю, это просто пример. Вот почему я назвал это придиркой. Кстати, конструктор base () может быть общедоступным :)





Все, что вы «новое», необходимо удалить, иначе вы вызовете утечку памяти. Итак, эти две строки:
psomeclass = new someclass();
pint = new int();
Вызовет утечку памяти, потому что вам нужно сделать:
delete pint;
delete psomeclass;
В блоке finally, чтобы избежать их утечки.
Также эта строка:
base temp = base();
Не нужно. Вам просто нужно сделать:
base temp;
Добавлять «= base ()» не нужно.
В C++ нет такого понятия, как блок «finally».
Правда, вы можете иметь или не иметь к нему доступ в зависимости от вашего вкуса C++ - в противном случае вам нужно будет убедиться, что выделения удаляются независимо от выбранного пути кода.
Ваше замечание о дополнительной инициализации неверно. Результирующий объект будет инициализирован только один раз и не будет скопирован.
Да, этот код приведет к утечке памяти. Блоки памяти, выделенные с помощью «нового», не освобождаются при возникновении исключения. Это часть мотивации 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;
}
вместо использования указателей с помощью объекта (интеллектуального указателя) ситуация улучшится? Поскольку, когда в блоке возникает исключение, автоматические объекты очищаются.
умные указатели лучше. Также замените «поднять»; с 'бросить;' Чтобы повторно выбросить текущее исключение.
Если вы добавляете конструктор, вы должны очистить все, что было до вызова throw. Если вы используете наследование или добавление деструктора, вам действительно не следует этого делать. Странное поведение (нет моего стандартного под рукой, но он может быть неопределенным?).
Не уверен, действительно ли он не определен, но, безусловно, очень опасен, потому что деструкторы вызываются во время раскрутки стека при возникновении исключения. Если вы вызовете исключение пока, возникшее другое исключение, каждая среда выполнения C++, которую я знаю, завершит работу приложения.
Неперехваченное исключение в деструкторе, созданном во время обработки исключений, вызывает вызов std :: terminate (), который по умолчанию вызывает std :: abort (). Поведение по умолчанию можно изменить.
даже несмотря на то, что поведение по умолчанию можно переопределить, ваша версия по-прежнему не может вернуться в приложение, она все равно должна выйти.
Обе новинки будут протекать.
Назначьте адрес созданных объектов кучи интеллектуальным указателям названный, чтобы он был удален внутри деструктора интеллектуальных указателей, вызываемого при возникновении исключения - (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 <>
// Изменен вопрос, чтобы иметь базу * temp = new base ();
И boost :: scoped_ptr <> может быть даже лучше, чем auto_ptr <>, у которого есть собственная банка червей.
Это был случайный выбор с точки зрения умных указателей в качестве примеров. Он достаточно общий, чтобы не беспокоиться о объяснении, когда его не следует использовать в таких быстрых примерах, как этот. Но да, если можно использовать более простые умные указатели, то используйте.
Да, это приведет к утечке памяти. Когда конструктор выбрасывает, деструктор не вызывается (в этом случае вы не показываете деструктор, который освобождает динамически выделенные объекты, но предположим, что он у вас есть).
Это основная причина использования интеллектуальных указателей - поскольку интеллектуальные указатели являются полноценными объектами, они будут вызывать деструкторы во время раскрутки стека исключения и иметь возможность освободить память.
Если вы используете что-то вроде шаблона 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();
утверждение):
Когда внутри конструктора генерируется исключение, вам следует принять во внимание несколько вещей, связанных с правильной обработкой выделения ресурсов, которые могли произойти при прерванном построении объекта:
Это означает, что если ваш объект владеет ресурсами, у вас есть 2 доступных метода для очистки тех ресурсов, которые могли быть уже получены, когда конструктор выбрасывает:
Разве перетаскивание в Boost просто и управление памятью не является довольно глупым?
Возможно, но scoped_ptr находится в TR1 и будет в C++ 09, так что это то, что все равно следует изучить. А часть Boost с scoped_ptr - это просто набор заголовков. Наконец, вы можете использовать auto_ptr вместо этого для этого простого примера, но auto_ptr, вероятно, следует избегать.
будет ли вызываться dtor базового класса, даже если у меня один раз? что произойдет с базой ниже линии * temp = new base ();
Ничего не случится с "base * temp = new base;" line - исключение приведет к освобождению памяти для попытки выделения нового базового объекта (даже если dtor не будет вызываться).
тогда нужно ли заботиться о том, чтобы освободить память об отдельном члене?
См. Добавленное мною резюме, которое, как мне кажется, должно пояснить причины этого.
Примечание: будут вызваны деструкторы ПОЛНОСТЬЮ сконструированных членов.
@Loki Можете ли вы определить «ПОЛНОСТЬЮ построенный»? Разве все члены, кроме a, не будут созданы во время выполнения строки throw?
@ Натан: Полностью: поток выполнения вышел из конструктора. В этом случае да, все элементы полностью построены. Но если член генерирует исключение во время своего создания (то есть во время списка инициализаторов), не все другие члены могут быть созданы. Поэтому, если вы используете объект base как член другого класса, у вас может возникнуть эта проблема.
вам нужно удалить psomeclass ... Нет необходимости очищать целое число ...
Не могли бы вы уточнить, Дэйв Мур? Это про часть "не нужно убирать целое"? Причина этого в том, что указатель памяти Int не стоит много по сравнению с указателем памяти класса, поэтому я сказал, что его не нужно очищать.
Они оба протекают; стоимость не проблема. Вопрос был в том, просочилась она или нет. И если этот кусок кода выполняется тысячи или миллионы раз, эта небольшая стоимость складывается. Даже если «стоимость» имеет значение, значение имеет не размер указатель, а размер объекта, на который указывает указатель. Например, это возможно для sizeof (someclass) == sizeof (int). И вы не удаляете указатель - вы удаляете указанную сущность.
Я считаю, что главный ответ неверен и все равно будет утечка памяти. Деструктор для членов класса будет вызван нет, если конструктор выдает исключение (потому что он никогда не завершал свою инициализацию, и, возможно, некоторые члены никогда не достигли своих вызовов конструктора). Их деструкторы вызываются только во время вызова деструктора класса. Это имеет смысл.
Эта простая программа демонстрирует это.
#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, деструкторы будут вызваны должным образом.
Я знаю, у меня ужасный характер, и я не могу не придираться к мелочам. Я ничего не могу поделать. Мои 2 цента: инструкция objsomeclass = someclass (); не нужно. В теле конструктора objsomeclass уже инициализирован по умолчанию. objsomeclass (someclass ()) ниже тоже не имеет смысла.