Конструкторы, деструкторы или альтернативные методы?
С чего бы начать?... Ем, ну... А, так вот! Вместе со становлением своего стиля кодинга постепенно менялись и принципы работы с екземплярами объектов. Под объектом я подрозумеваю некую структуру. К етой некой прилагаются процедуры обслужывающиее её. Сначала (ох, давно ето было) я представлял в программе объекты как... нет, мне стыдно, немогу сказать) Поскольку я когдато был противником ООП-заточеных языков, то и реализация классов у мну была своя. К примеру:
typedef struct CLASS {
int a, b;
} CLASS;
// class.c
#include "class.h"
int class_create (CLASS **pcls) {
if (!(*pcls = malloc (sizeof (CLASS))))
goto fail1;
memset (*pcls, 0, sizeof (CLASS));
return 1;
fail1: return 0;
}
void class_release (CLASS *pcls) {
free (*pcls); *pcls = 0;
}
// тут все остальные методы...
Чем больше становилось объектов в моих программах - тем лучше я понимал что изобретать велосипеды нехорошо. И вот в один прекрасный (или не очень) день я глянул в сторону ООП языков. Т.к. знаю я только Си то естественно что взгляд мой пал на всплюснутую его разновидность. Если взять предыдущий пример и переписать его на Си++ то всё пучком, но. Основную часть создаваемых мною классов составляют сложные классы. Ну что-то типа (всё еще в велосипедном варианте):
typedef struct CLASSA {
void *p;
} CLASSA;
// class_a.c
#include "class_a.h"
int classa_create (CLASSA **pcls) {
CLASSA *cls;
if (!(cls = *pcls = malloc (sizeof (CLASSA))
fail1;
memset (cls, 0, sizeof (CLASSA));
if (!(cls->p = malloc (4096))
goto fail2;
return 1;
fail2: free (cls); *pcls = 0;
fail1: return 0;
}
void classa_release (CLASSA **pcls) {
CLASSA *cls = *pcls;
free (cls->p);
free (cls); *pcls = 0;
}
// class_b.h
typedef struct CLASSB {
CLASSA *a;
} CLASSB;
// class_b.c
#include "class_a.h"
#include "class_b.h"
int classb_create (CLASSB **pcls) {
CLASSB *cls;
if (!cls = *pcls = malloc (sizeof (CLASSB)))
goto fail1;
memset (cls, 0, sizeof (CLASSB));
if (!classa_create (&cls->a))
goto fail2;
return 1;
fail2: free (cls); *pcls = 0;
fail1: return 0;
}
Здесь можно созерцать картину когда благополучие создания екземпляра класса Б зависит от успешности создания екземпляра класса А, а класса А от успешности выделения памяти. Всё бы хорошо, но если глянуть на все ето со стороны Си++ то напрашиватся такой код:
typedef class CLASSA {
public:
CLASSA ();
~CLASSA ();
void *p;
} CLASSA;
// class_a.cpp
#include "class_a.h"
CLASSA::CLASSA () {
memset (this, 0, sizeof (CLASSA));
if (!(p = malloc (4096)))
goto fail1;
// надо бы как-то известить об успешном
// завершении... но как?
return;
fail1: // а здесь надо сказать о трагедии...
;
}
CLASSA::~CLASSA () {
free (p);
}
Со вторым классом похожая картина. Вообщем решением проблемы стали альтернативные "конструкторы" и "деструкторы". А именно:
typedef class CLASSA {
public:
CLASSA ();
static CLASSA* create ();
void release ();
void *p;
} CLASSA;
// class_a.cpp
#include "class_a.h"
CLASSA::CLASSA () {
memset (this, 0, sizeof (CLASSA));
}
CLASSA* CLASSA::create () {
CLASSA *cls;
if (!(cls = new CLASSA))
goto fail1;
if (!(cls->p = malloc (4096)))
goto fail2;
return cls;
fail2: delete CLASSA;
fail1: return 0;
}
void CLASSA::release () {
free (p);
delete this;
}
Может быть всё ето жуткий ужос, но я только начинаю осваиватся в ООПрограммировании.
Собственно вопрос: а кто как делает? Может на всё ето есть человечиское решение? Или может быть всё то шо выше не такой уж и ужос?
Вы пока приблизились только к инкапсуляции.
Когда почувствуете, что Вам для реализации Ваших идей требуется наследование (да еще и множественное) и полиморфизм, то стиль Вашего кодирования изменится еще раз ;) и станет более привычным и понятным.
Хотя всех трех китов можно реализовать на чистом C (примерно как у Вас), но для реальных задач это будет излишней тратой денег и нервов в ущерб понятности и корректности проекта.
ps. это ужас IMHO! :) все же обратитесь к правильным методам описания классов и их реализации. Гради Буч вам в помощь!
// надо бы как-то известить об успешном
// завершении... но как?
попробуйте исключения
Нипавериш! Перед написанием топика думал об етом, но забыл внести в топик. Из головы вылетело.
Что касается исключений, то мне етот механизм просто не нравится. Ибо если каждый раз при создании объекта печатать [FONT="Courier New"]try {...} catch (...) {...}[/FONT] ето получится ИМХО слишком громоздко.
А постоянно проверять валидность результата с описанием реакции программы - не громоздко?
В этом разница.
Если при создании класса всё же нужно выполнить операцию, которая может выбросить исключение, то традиционным способом считается выполнение этой операции в методе типа Init (или Create). Так что, Bard, ход мыслей в целом правильный, имхо.
А подробнее, кстати?
Я вот в дельфи делаю так:
try
MyCoolObject.DoSomenthingVeryVeryCool();
finally
FreeAndNil(MyCoolObject);
end;
Понятно, что в языках со сборщиками мусоров уничтожать объект вручную не нужно (или таки бывает, что нужно?). Но вроде сиязыки не из тех, что со сборщиками ентими (Сишарп не щитаем, он левый :)).
if Assigned(MyObject) then // Либо if MyObject <> nil then
...
Т.е. конструктор должен возвращать пустой указатель, если объект не создался корректно.
if Assigned(MyObject) then // Либо if MyObject <> nil then
...
Т.е. конструктор должен возвращать пустой указатель, если объект не создался корректно.
В С++ проверка на 0 не гарантируется.
Если new не сработает - ptr не обязательно будет равен 0. Стандарт этого не гарантирует. Так же и после delete, кстати.
Кошерно (в этом случае) ловить bad_alloc.
Бывает нужно, если требуется освободить дескриптор файла к примеру. Т.к. сборщик мусора выполняет сборку мусора в тот момент, когда ему начинает не хватать выделенной памяти, следовательно если оставить освобождение дескриптора на сборщик мусора, то момент его освобождения будет, вообще говоря, не определён.
Это не уничтожение объекта. Это детерминированное освобождение ресурсов (вызов в конечном счете CloseHandle). Память под объект как была выделена, так и остается.
Ну да, если быть точным именно так. Но оставлять это на сборщик мусора нельзя и делать это нужно руками. Но, если под освобаждаемым ресурсом понимать не только память в куче, но и захваченные дескрипторы, то такая ситуация будет(на мой взгляд) аналогична тому же вызову delete в C++.
Разница лишь в том, какой ресурс освобождаем - в C++ это будет память в куче\дескриптор файла, в языке со сборщиком мусора - дескриптор. В итоге то происходит одно и то же - программа освобождает некий захваченный ресурс в общее пользование и обработать это "возвращение" должен программист, т.к. сборщик мусора может сработать не корректно.
Зачем это нужно? Кощунство же.
Думаю, лучше позволить конструктору самому выделить память, а исключение (bad_alloc) ловить снаружи. При этом ловить не там, где создаём любой произвольный объект, а там где можем отработать аварийную ситуацию.
Методом тыка я определил что [FONT="Courier New"]new[/FONT] не очищает выделенную память. А проставлять ручками все елементы класса в дефолтное положение неохота.
И если говорить об исключениях то как же быть с накоплением ресурсов? Конечно можно перед каждым [FONT="Courier New"]throw[/FONT] освобождать все успешно созданные объекты но... я люблю красивый код)
А почему вы решили, что this должен указывать на "очищенную" память?
Что создали в конструкторе, то удаляйте в деструкторе.
А если система не даёт успешно удалить ресурсы, то как вы их собираетесь удалять?
Да, ето мысль. Тоесть ето удаление неуспешно созданного объекта? Я правильно понял?
Не все классы так можно "задефолтить". Теоретически, POD-структуры можно, но не любой класс.
Ето так. Но почти все мои определения POD'истые.
Я говорил не об аварийных ситуациях.
Объект не считается созданным до тех пор, пока не завершится работа его конструктора. В случае невозможности выделения памяти конструктор сам вызовет деструкторы для внутренних объектов, которые уже успел создать. Но это не относится к памяти, выделенной с помощью new.
В качестве альтернативы чистому new можно задействовать std::auto_ptr, например, или std::vector. Для них дестукторы определены.
Надеюсь, не наврал.
Рекомендую: Страуструп, специальное издание, стр 419 :)
Я так не думал. То что [FONT="Courier New"]memset (this, ...)[/FONT] стоит в конструкторе - ето чисто для автоматизации затирания памяти. Я могу тоже самое написать и в [FONT="Courier New"]create()[/FONT], только затирать вместо [FONT="Courier New"]this[/FONT] там будет [FONT="Courier New"]ptr[/FONT] на только что созданный объект.
Но вот сейчас глянул как у меня в конструкторе красуется один лиш [FONT="Courier New"]memset[/FONT] да еще и затирающий [FONT="Courier New"]this[/FONT]... на душе похолодело. Согласен, кощунство)
Но вот сейчас глянул как у меня в конструкторе красуется один лиш [FONT="Courier New"]memset[/FONT] да еще и затирающий [FONT="Courier New"]this[/FONT]... на душе похолодело. Согласен, кощунство)
Это не просто кощунство, а преступление. :)
Представь себе, что сделает memset в такой ситуации:
{
std::list<type> b;
}
или в такой:
{
virtual void func();
}
даже страшно подумать :)
Представь себе, что сделает memset в такой ситуации:
{
std::list<type> b;
}
или в такой:
{
virtual void func();
}
даже страшно подумать :)
Может и страшно, но я вовсе не предполагаю что [FONT="Courier New"]memset[/FONT] решает весь вопрос инициализации. Просто она обнуляет все [FONT="Courier New"]int[/FONT], [FONT="Courier New"]float[/FONT] и д.т. члены класса. А на всякие там [FONT="Courier New"]ptr'[/FONT]ы и дочерние объекты есть свои строки в коде инициализатора.
Что касается каких-то "умных" пойнтеров - не вкурсе)
http://ru.wikipedia.org/wiki/Умный_указатель
http://www.cplusplus.com/reference/std/memory/auto_ptr/
Не фильтруйте советы: читайте книжки, читайте стандарт C++ (в сети был доступен черновик). Если будет мало, то посмотрите исходники auto_ptr (или умных указателей из boost). Обратите внимание на контейнеры STL, как на заменители динамически создаваемых массивов.
А как ты выборочно сделаешь memset только для POD ?
"Дочерние объекты" иннициализируюся до входа в код конструктора класса.
"Дочерние объекты" иннициализируюся до входа в код конструктора класса.
Туплю...
*пошел учить матчасть*