Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Конструкторы, деструкторы или альтернативные методы?

282
09 февраля 2011 года
Bard
481 / / 26.02.2006
Всем доброго времени суток!
С чего бы начать?... Ем, ну... А, так вот! Вместе со становлением своего стиля кодинга постепенно менялись и принципы работы с екземплярами объектов. Под объектом я подрозумеваю некую структуру. К етой некой прилагаются процедуры обслужывающиее её. Сначала (ох, давно ето было) я представлял в программе объекты как... нет, мне стыдно, немогу сказать) Поскольку я когдато был противником ООП-заточеных языков, то и реализация классов у мну была своя. К примеру:

Код:
// class.h

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;

}

// тут все остальные методы...



Чем больше становилось объектов в моих программах - тем лучше я понимал что изобретать велосипеды нехорошо. И вот в один прекрасный (или не очень) день я глянул в сторону ООП языков. Т.к. знаю я только Си то естественно что взгляд мой пал на всплюснутую его разновидность. Если взять предыдущий пример и переписать его на Си++ то всё пучком, но. Основную часть создаваемых мною классов составляют сложные классы. Ну что-то типа (всё еще в велосипедном варианте):


Код:
// class_a.h

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;

}



Здесь можно созерцать картину когда благополучие создания екземпляра класса Б зависит от успешности создания екземпляра класса А, а класса А от успешности выделения памяти. Всё бы хорошо, но если глянуть на все ето со стороны Си++ то напрашиватся такой код:

Код:
// class_a.h

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);

}



Со вторым классом похожая картина. Вообщем решением проблемы стали альтернативные "конструкторы" и "деструкторы". А именно:


Код:
// class_a.h

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;

}



Может быть всё ето жуткий ужос, но я только начинаю осваиватся в ООПрограммировании.

Собственно вопрос: а кто как делает? Может на всё ето есть человечиское решение? Или может быть всё то шо выше не такой уж и ужос?
412
09 февраля 2011 года
grgdvo
323 / / 04.07.2007
ООП - это три кита: инкапсуляция, наследование и полиморфизм...
Вы пока приблизились только к инкапсуляции.
Когда почувствуете, что Вам для реализации Ваших идей требуется наследование (да еще и множественное) и полиморфизм, то стиль Вашего кодирования изменится еще раз ;) и станет более привычным и понятным.
Хотя всех трех китов можно реализовать на чистом C (примерно как у Вас), но для реальных задач это будет излишней тратой денег и нервов в ущерб понятности и корректности проекта.

ps. это ужас IMHO! :) все же обратитесь к правильным методам описания классов и их реализации. Гради Буч вам в помощь!
1.8K
09 февраля 2011 года
LM(AL/M)
332 / / 20.12.2005
Цитата: Bard

// надо бы как-то известить об успешном
// завершении... но как?



попробуйте исключения

11
09 февраля 2011 года
oxotnik333
2.9K / / 03.08.2007
Мда... вот поэтому то и говорят, что Це и ЦеПП разные языки а не один дополнение другого.
282
09 февраля 2011 года
Bard
481 / / 26.02.2006
Цитата: LM(AL/M)
попробуйте исключения



Нипавериш! Перед написанием топика думал об етом, но забыл внести в топик. Из головы вылетело.

Что касается исключений, то мне етот механизм просто не нравится. Ибо если каждый раз при создании объекта печатать [FONT="Courier New"]try {...} catch (...) {...}[/FONT] ето получится ИМХО слишком громоздко.

9
09 февраля 2011 года
Lerkin
3.0K / / 25.03.2003
Цитата: Bard
Что касается исключений, то мне етот механизм просто не нравится. Ибо если каждый раз при создании объекта печатать [FONT="Courier New"]try {...} catch (...) {...}[/FONT] ето получится ИМХО слишком громоздко.


А постоянно проверять валидность результата с описанием реакции программы - не громоздко?

6
10 февраля 2011 года
George
4.1K / / 05.01.2007
А мне казалось, что в дельфи при создании объекта завернуть его в try--finally - это святое. Ну, чтобы он потом гарантированно изничтожился. Ведь ладно, если один объект, а если тысячи их? Ну в цикле например создаются, или как в ORM на основе записей из БД. Копец обеспечен. :) Громоздко-негромоздко, а надо. Хотя может быть в этих ваших сиязыках что-то по другому, но сомневаюсь.
9
10 февраля 2011 года
Lerkin
3.0K / / 25.03.2003
Цитата: George
Ну, чтобы он потом гарантированно изничтожился.


В этом разница.

297
10 февраля 2011 года
koodeer
1.2K / / 02.05.2009
В конструкторе класса исключение выбрасываться не должно. Причины этого неоднократно обсуждались, об этом много писалось. конструктор исключение c++

Если при создании класса всё же нужно выполнить операцию, которая может выбросить исключение, то традиционным способом считается выполнение этой операции в методе типа Init (или Create). Так что, Bard, ход мыслей в целом правильный, имхо.
6
10 февраля 2011 года
George
4.1K / / 05.01.2007
Цитата: Lerkin
В этом разница.

А подробнее, кстати?
Я вот в дельфи делаю так:

 
Код:
MyCoolObject := TMyCoolObject.Create;
try
  MyCoolObject.DoSomenthingVeryVeryCool();
finally
  FreeAndNil(MyCoolObject);
end;

Понятно, что в языках со сборщиками мусоров уничтожать объект вручную не нужно (или таки бывает, что нужно?). Но вроде сиязыки не из тех, что со сборщиками ентими (Сишарп не щитаем, он левый :)).
6
10 февраля 2011 года
George
4.1K / / 05.01.2007
Хотя мой вопрос не по теме. Если нужно проверять, благополучно ли создался объект, то все еще проще вроде.
 
Код:
MyObject := TMyObject.Create;
if Assigned(MyObject) then // Либо if MyObject <> nil then
...

Т.е. конструктор должен возвращать пустой указатель, если объект не создался корректно.
9
10 февраля 2011 года
Lerkin
3.0K / / 25.03.2003
Цитата: George
Хотя мой вопрос не по теме. Если нужно проверять, благополучно ли создался объект, то все еще проще вроде.
 
Код:
MyObject := TMyObject.Create;
if Assigned(MyObject) then // Либо if MyObject <> nil then
...

Т.е. конструктор должен возвращать пустой указатель, если объект не создался корректно.


В С++ проверка на 0 не гарантируется.

 
Код:
SomeType* ptr = new SomeType;

Если new не сработает - ptr не обязательно будет равен 0. Стандарт этого не гарантирует. Так же и после delete, кстати.
Кошерно (в этом случае) ловить bad_alloc.
2.1K
10 февраля 2011 года
Norgat
452 / / 12.08.2009
Цитата: George
Понятно, что в языках со сборщиками мусоров уничтожать объект вручную не нужно (или таки бывает, что нужно?).



Бывает нужно, если требуется освободить дескриптор файла к примеру. Т.к. сборщик мусора выполняет сборку мусора в тот момент, когда ему начинает не хватать выделенной памяти, следовательно если оставить освобождение дескриптора на сборщик мусора, то момент его освобождения будет, вообще говоря, не определён.

5
10 февраля 2011 года
hardcase
4.5K / / 09.08.2005
Цитата: Norgat
Бывает нужно, если требуется освободить дескриптор файла к примеру.

Это не уничтожение объекта. Это детерминированное освобождение ресурсов (вызов в конечном счете CloseHandle). Память под объект как была выделена, так и остается.

2.1K
10 февраля 2011 года
Norgat
452 / / 12.08.2009
Цитата: hardcase
Это не уничтожение объекта. Это детерминированное освобождение ресурсов (вызов в конечном счете CloseHandle). Память под объект как была выделена, так и остается.



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

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

6
10 февраля 2011 года
George
4.1K / / 05.01.2007
Ну я щетал, что сборщик мусора - это про объехты.
87
10 февраля 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Bard

 
Код:
memset (this, 0, sizeof (CLASSA));


Зачем это нужно? Кощунство же.

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

282
10 февраля 2011 года
Bard
481 / / 26.02.2006
Цитата: Kogrom
Зачем это нужно? Кощунство же.



Методом тыка я определил что [FONT="Courier New"]new[/FONT] не очищает выделенную память. А проставлять ручками все елементы класса в дефолтное положение неохота.

И если говорить об исключениях то как же быть с накоплением ресурсов? Конечно можно перед каждым [FONT="Courier New"]throw[/FONT] освобождать все успешно созданные объекты но... я люблю красивый код)

87
10 февраля 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Bard
Методом тыка я определил что [FONT="Courier New"]new[/FONT] не очищает выделенную память. А проставлять ручками все елементы класса в дефолтное положение неохота.


А почему вы решили, что this должен указывать на "очищенную" память?

Цитата: Bard
И если говорить об исключениях то как же быть с накоплением ресурсов? Конечно можно перед каждым [FONT="Courier New"]throw[/FONT] освобождать все успешно созданные объекты но... я люблю красивый код)


Что создали в конструкторе, то удаляйте в деструкторе.
А если система не даёт успешно удалить ресурсы, то как вы их собираетесь удалять?

282
10 февраля 2011 года
Bard
481 / / 26.02.2006
Цитата: Kogrom
Что создали в конструкторе, то удаляйте в деструкторе.



Да, ето мысль. Тоесть ето удаление неуспешно созданного объекта? Я правильно понял?

87
10 февраля 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Bard
А проставлять ручками все елементы класса в дефолтное положение неохота.



Не все классы так можно "задефолтить". Теоретически, POD-структуры можно, но не любой класс.

282
10 февраля 2011 года
Bard
481 / / 26.02.2006
Цитата: Kogrom
Не все классы так можно "задефолтить". Теоретически, POD-структуры можно, но не любой класс.



Ето так. Но почти все мои определения POD'истые.

87
11 февраля 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Bard
Да, ето мысль. Тоесть ето удаление неуспешно созданного объекта? Я правильно понял?


Я говорил не об аварийных ситуациях.

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

В качестве альтернативы чистому new можно задействовать std::auto_ptr, например, или std::vector. Для них дестукторы определены.

Надеюсь, не наврал.
Рекомендую: Страуструп, специальное издание, стр 419 :)

282
11 февраля 2011 года
Bard
481 / / 26.02.2006
Цитата: Kogrom
А почему вы решили, что this должен указывать на "очищенную" память?



Я так не думал. То что [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]... на душе похолодело. Согласен, кощунство)

5
11 февраля 2011 года
hardcase
4.5K / / 09.08.2005
Я все жду когда вспомнят об умных указателях, чтобы вообще деструктор не требовался в таких случаях.
3
11 февраля 2011 года
Green
4.8K / / 20.01.2000
Цитата: Bard
Я так не думал. То что [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]... на душе похолодело. Согласен, кощунство)


Это не просто кощунство, а преступление. :)
Представь себе, что сделает memset в такой ситуации:

 
Код:
class A
{
    std::list<type> b;
}

или в такой:
 
Код:
class B
{
    virtual void func();
}

даже страшно подумать :)
87
11 февраля 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Kogrom
В качестве альтернативы чистому new можно задействовать std::auto_ptr, например



Цитата: hardcase
Я все жду когда вспомнят об умных указателях, чтобы вообще деструктор не требовался в таких случаях.



Жди :)

282
11 февраля 2011 года
Bard
481 / / 26.02.2006
Цитата: Green
Это не просто кощунство, а преступление. :)
Представь себе, что сделает memset в такой ситуации:
 
Код:
class A
{
    std::list<type> b;
}

или в такой:
 
Код:
class B
{
    virtual void func();
}

даже страшно подумать :)



Может и страшно, но я вовсе не предполагаю что [FONT="Courier New"]memset[/FONT] решает весь вопрос инициализации. Просто она обнуляет все [FONT="Courier New"]int[/FONT], [FONT="Courier New"]float[/FONT] и д.т. члены класса. А на всякие там [FONT="Courier New"]ptr'[/FONT]ы и дочерние объекты есть свои строки в коде инициализатора.

Что касается каких-то "умных" пойнтеров - не вкурсе)

87
11 февраля 2011 года
Kogrom
2.7K / / 02.02.2008
Цитата: Bard
Что касается каких-то "умных" пойнтеров - не вкурсе)



http://ru.wikipedia.org/wiki/Умный_указатель
http://www.cplusplus.com/reference/std/memory/auto_ptr/

Не фильтруйте советы: читайте книжки, читайте стандарт C++ (в сети был доступен черновик). Если будет мало, то посмотрите исходники auto_ptr (или умных указателей из boost). Обратите внимание на контейнеры STL, как на заменители динамически создаваемых массивов.

3
12 февраля 2011 года
Green
4.8K / / 20.01.2000
Цитата: Bard
Может и страшно, но я вовсе не предполагаю что [FONT="Courier New"]memset[/FONT] решает весь вопрос инициализации. Просто она обнуляет все [FONT="Courier New"]int[/FONT], [FONT="Courier New"]float[/FONT] и д.т. члены класса. А на всякие там [FONT="Courier New"]ptr'[/FONT]ы и дочерние объекты есть свои строки в коде инициализатора.


А как ты выборочно сделаешь memset только для POD ?
"Дочерние объекты" иннициализируюся до входа в код конструктора класса.

282
12 февраля 2011 года
Bard
481 / / 26.02.2006
Цитата: Green
А как ты выборочно сделаешь memset только для POD ?
"Дочерние объекты" иннициализируюся до входа в код конструктора класса.




Туплю...

*пошел учить матчасть*

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог