Я упомянул в одном из моих более ранних вопросов, что читаю книгу "Стандарты Кодирования C++" Herb Sutter и Andrei Alexandrescu. В одной из глав они говорят что-то вроде этого:
Всегда выполняйте приобретение неуправляемого ресурса, такое как новое выражение, результат которого сразу не передается конструктору интеллектуального указателя в теле конструктора а не в списках инициализатора.
Делает это означает, что я должен использовать конструкцию этой формы (если это data_3_ должно быть инициализировано с новым):
SomeClass(const T& value, const U& value2, const R& value3)
: data_(value), data_2_(value2)
{
data_3_ = new value3;
}
вместо:
SomeClass(const T& value, const U& value2, const R& value3)
: data_(value), data_2_(value2), data_3_(new value3)
// here data_3_ is initialized in ctor initialization list
// as far as I understand that incorrect way according to authors
{
}
Заранее спасибо.
P.S. И если это - то, что они имеют в виду, почему они используют приобретение неуправляемого ресурса термина? Я всегда думал, что это снабжает, "вручную управляются"?
P.S. 2. Я сожалею заранее, если существуют какие-либо проблемы форматирования в этом сообщении - я должен признать - я абсолютно терпеть не могу способ отформатировать на этом форуме.
Это потому, что конструктор SomeClass может сделать исключение.
В описанной вами ситуации (т.е. не используя умный указатель), вы должны освободить ресурс в деструкторе И если конструктор SomeClass бросает исключение, то с блоком try-catch:
SomeClass(const T& value, const U& value2, const R& value3):data_(value),data_2_(value2) :
data_3_(NULL)
{
try
{
data_3_ = new value3;
// more code here that may throw an exception
}
catch(...)
{
delete data_3_;
throw;
}
}
... Что нельзя сделать, если в списке инициализации брошено исключение.
Смотрите this для дальнейших пояснений.
.Я пишу это как ответ, потому что он слишком длинный, чтобы поместиться в комментарий.
Рассмотрим:
A * a;
...
a = new A;
Что произойдет, если бросит конструктор A?
Из этого должно быть очевидно, что нечего вызывать delete, нет выделенной памяти, нет объекта типа A. Та же самая логика действует и в случае новых бросков, за исключением того, что конструктор A никогда не будет использоваться в первую очередь.
.Совет необходим, если класс содержит два или более неуправляемых ресурса. Если выделение одного не удается, то во избежание утечки необходимо освободить все ранее выделенные ресурсы. (EDIT: в более общем случае, любое исключение, брошенное после выделения ресурса, должно быть обработано путем удаления этого ресурса). Это невозможно сделать, если они распределены в списке инициализаторов. Например:
SomeClass() : data1(new value1), data2(new value2) {}
приведет к утечке значения value1
, если будет выбрано новое значение2
. Вам нужно будет разобраться с этим так:
SomeClass() : data1(0), data2(0)
{
data1 = new value1; // could be in the initialiser list if you want
try
{
data2 = new value2;
}
catch (...)
{
delete data1;
throw;
}
}
Конечно, всех этих махинаций можно избежать, разумно используя умные указатели.
.Инициализация ресурсов, управляемых вручную, может привести к утечке ресурсов, если конструктор бросит исключение на любом этапе.
Сначала рассмотрим этот код с автоматически управляемыми ресурсами:
class Breakfast {
public:
Breakfast()
: spam(new Spam)
, sausage(new Sausage)
, eggs(new Eggs)
{}
~Breakfast() {}
private:
// Automatically managed resources.
boost::shared_ptr<Spam> spam;
boost::shared_ptr<Sausage> sausage;
boost::shared_ptr<Eggs> eggs;
};
Если выбрасывается "new Eggs"
, то не вызывается ~Breakfast
, но вызываются деструкторы всех членов конструкции в обратном порядке, то есть деструкторы колбасы
и спама
.
Все ресурсы освобождаются правильно, никаких проблем здесь нет.
Если вы используете сырые указатели (управляемые вручную):
class Breakfast {
public:
Breakfast()
: spam(new Spam)
, sausage(new Sausage)
, eggs(new Eggs)
{}
~Breakfast() {
delete eggs;
delete sausage;
delete spam;
}
private:
// Manually managed resources.
Spam *spam;
Sausage *sausage;
Eggs *eggs;
};
Если "новые яйца"
бросает, помните, что ~Breakfast
не вызывается, а деструкторы спама
и колбасы
(которые в этом случае ничего не стоят, потому что сырые указатели у нас как реальные объекты).
Поэтому у вас утечка.
Правильным способом переписывания кода выше является следующее:
class Breakfast {
public:
Breakfast()
: spam(NULL)
, sausage(NULL)
, eggs(NULL)
{
try {
spam = new Spam;
sausage = new Sausage;
eggs = new Eggs;
} catch (...) {
Cleanup();
throw;
}
}
~Breakfast() {
Cleanup();
}
private:
void Cleanup() {
// OK to delete NULL pointers.
delete eggs;
delete sausage;
delete spam;
}
// Manually managed resources.
Spam *spam;
Sausage *sausage;
Eggs *eggs;
};
Конечно, вместо этого вы должны предпочесть обернуть каждый неуправляемый ресурс в отдельный RAII класс, чтобы вы могли управлять им автоматически и группировать его в другие классы.
.Это вопрос безопасности исключений.
Если по какой-то причине конструктор выходит из строя и выбрасывает исключение, то вы не можете очистить за собой.
Рассмотрим:
SomeClass() : data1(new T1()), data2(new T2()), data3(new T3()) {}
Если выбрасываются конструкторы T2
или T3
, то определенно происходит утечка памяти, соответствующей инициализации данных data1
. Также вы не будете знать, какое распределение вызвало исключение: new T2()
или new T3()
? и в таком случае вы не будете знать, безопасно ли удалять данные2;
в составе обработчика исключений конструктора.
Для написания безопасного кода исключения используйте умные указатели или используйте try
/catch
блоки в теле конструктора.
SomeClass() : data1(new T1()), data2(new T2()), data3(new T3())
{
data1 = new T1();
try
{
data2 = new T2();
try
{
data3 = new T3();
}
catch (std::exception&)
{
delete data2;
throw;
}
}
catch (std::exception&)
{
delete data1;
throw;
}
}
Как вы видите, использование try
/catch
блоков не настолько читабельно и вероятно склонно к ошибкам, по сравнению с использованием умных указателей-членов.
Замечание: "Стандарты кодирования C++" Глава 48 ссылается на "Более исключительные C++". Пункт 18, который сам по себе ссылается на статью Stroustrup "Проектирование и эволюция C++3", раздел 16.5, и статью Stroustrup "Язык программирования C++", раздел 14.4.
EDIT: "Более исключительный C++ пункт 18 имеет то же содержание, что и GotW #66: Неисправности конструктора". Обратитесь к веб-странице, если у вас нет книги.
.