может ли класс удалить сам себя
От: win_ce  
Дата: 10.09.09 13:53
Оценка:
Столкнулся с такой проблемой: есть класс State который переключает диалоги, есть классы диалогов Dlg1 и Dlg2 производные от CDialog (MFC).
Диалоги дергают соответствующие методы State, а метод удаляет текущий диалог и создает новый. Т.е. получается, что диалог косвенно удаляет сам себя.
Соответственно если обращаться к State как в Dlg1, где вызов onNext1 это последняя функция, все вроде бы работает нормально. А если как в диалоге Dlg2, где после вызова onNext2, а значит и удаления объекта, есть ещё какой-то код получаю ""First-chance exception".
Собственно вопрос, это нормально и можно ли в принципе удалять самого себя или я перемудрил?


class State
{
    void onNext1()
    {
        delete dlg_;
        dlg_ = new Dlg2(this);    
    }

    void onNext2()
    {
        delete dlg_;
        dlg_ = new Dlg1(this);    
    }

private:
    CDialog *dlg_;
};



class Dlg1
{
public:
    Dlg1(State *state) : state_(state) {}
    void onNext()
    {
        state_->onNext1();
    }
private:
    State *state_;    
};



class Dlg2
{
public:
    Dlg1(State *state) : state_(state) {}
    void onNext()
    {
        state_->onNext2();
        ...
        ещё какой-то код    
    }
private:
    State *state_;    
};
Re: может ли класс удалить сам себя
От: Sni4ok  
Дата: 10.09.09 14:16
Оценка:
Здравствуйте, win_ce, Вы писали:
_>Собственно вопрос, это нормально и можно ли в принципе удалять самого себя или я перемудрил?

удалять себя можно, только после удаления уже нельзя обращаться к this'у и вызывать нестатические методы этого обьекта.
Re: может ли класс удалить сам себя
От: Владислав Курмаз Украина http://tis-method.org/
Дата: 10.09.09 14:42
Оценка:
Здравствуйте, win_ce, Вы писали:

_>Столкнулся с такой проблемой: есть класс State который переключает диалоги, есть классы диалогов Dlg1 и Dlg2 производные от CDialog (MFC).

_>Диалоги дергают соответствующие методы State, а метод удаляет текущий диалог и создает новый. Т.е. получается, что диалог косвенно удаляет сам себя.
_>Соответственно если обращаться к State как в Dlg1, где вызов onNext1 это последняя функция, все вроде бы работает нормально. А если как в диалоге Dlg2, где после вызова onNext2, а значит и удаления объекта, есть ещё какой-то код получаю ""First-chance exception".
_>Собственно вопрос, это нормально и можно ли в принципе удалять самого себя или я перемудрил?

_>

_>class State
_>{
_>    void onNext1()
_>    {
_>        delete dlg_;
_>        dlg_ = new Dlg2(this);    
_>    }

_>    void onNext2()
_>    {
_>        delete dlg_;
_>        dlg_ = new Dlg1(this);    
_>    }

_>private:
_>    CDialog *dlg_;
_>};

_>


_>

_>class Dlg1
_>{
_>public:
_>    Dlg1(State *state) : state_(state) {}
_>    void onNext()
_>    {
_>        state_->onNext1();
_>    }
_>private:
_>    State *state_;    
_>};

_>


_>

_>class Dlg2
_>{
_>public:
_>    Dlg1(State *state) : state_(state) {}
_>    void onNext()
_>    {
_>        state_->onNext2();
_>        ...
_>        ещё какой-то код    
_>    }
_>private:
_>    State *state_;    
_>};

_>


Здравствуйте.
Объект конечно может удалять себя. В данном случае важно время удаления.
Насколько я понял, Вы реализуете систему с использованием паттерна "State".
В его классической формулировке ничего не сказано кто отвечает за создание и удаление состояний.
Это как раз основная проблема всех реализаций — сохранение контекста вызова.
К тому же в более сложных примерах последовательность смены диалогов может быть не столь тривиальной, поэтому
решение должно быть универсальным.

Симптом: невозможно гарантировать последовательность разрушения объектов.
Решение: применение подсчёта ссылок.

Ниже пример с подсчётом ссылок, при желании можно использовать шарептр-ы ( пример набросал, но не компилил )
Следующая проблема, которуя я вижу при такой реализации паттерна это соответствие внутренней переменной State::dlg_ и текущему коду метода.
Комментарии в примере я добавил. Это может проявиться в более сложных каскадных вызовах семейства функуий onNext() друг из друга.

class State
{
public:
void onNext1()
{
ChangeDialog( new Dlg2(this) );
}

void onNext2()
{
ChangeDialog( new Dlg1(this) );
}
protected:
void ChangeDialog( CDialog* dlg )
{
dlg_->unref();
dlg_ = dlg;
}
private:
CDialog *dlg_;
};

class Dlg1
{
public:
Dlg1(State *state) : state_(state) {}
void onNext()
{
ref();
{
state_->onNext1();
}
unref();
}
private:
State *state_;
};

class Dlg2
{
public:
Dlg1(State *state) : state_(state) {}
void onNext()
{
ref();
{
state_->onNext2();
...
ещё какой-то код //????????????? вот тут ОСТОРОЖНО, текущий диалог State::dlg_ уже не Dlg2

}
unref();
}
private:
State *state_;
};
Re[2]: может ли класс удалить сам себя
От: Владислав Курмаз Украина http://tis-method.org/
Дата: 10.09.09 14:48
Оценка: 4 (1)
Форматирование не указал


class State
{
public:
    void onNext1()
    {
        ChangeDialog( new Dlg2(this) );
    }

    void onNext2()
    {
        ChangeDialog( new Dlg1(this) );
    }
protected:
    void ChangeDialog( CDialog* dlg )
    {
        dlg_->unref();
        dlg_ = dlg;
    }
private:
    CDialog *dlg_;
};

class Dlg1
{
public:
    Dlg1(State *state) : state_(state) {}
    void onNext()
    {
    ref();
    {
        state_->onNext1();
    }
    unref();
}
private:
    State *state_; 
};

class Dlg2
{
public:
    Dlg1(State *state) : state_(state) {}
    void onNext()
    {
        ref();
        {
            state_->onNext2();
            ...
            ещё какой-то код //????????????? вот тут ОСТОРОЖНО, текущий диалог State::dlg_ уже не Dlg2
        }
        unref();
    }
private:
State *state_; 
};
Re: может ли класс удалить сам себя
От: Vamp Россия  
Дата: 10.09.09 16:29
Оценка: -3
Во-первых, не класс, а объект.
Во-вторых, тема обсуждалась неоднократно: http://www.google.com/search?hl=en&q=%22delete+this%22+c%2B%2B&aq=f&oq=&aqi=
Да здравствует мыло душистое и веревка пушистая.
Re: может ли класс удалить сам себя
От: Кодт Россия  
Дата: 10.09.09 18:06
Оценка: 6 (1)
Здравствуйте, win_ce, Вы писали:

_>Столкнулся с такой проблемой: есть класс State который переключает диалоги, есть классы диалогов Dlg1 и Dlg2 производные от CDialog (MFC).

_>Диалоги дергают соответствующие методы State, а метод удаляет текущий диалог и создает новый. Т.е. получается, что диалог косвенно удаляет сам себя.
_>Соответственно если обращаться к State как в Dlg1, где вызов onNext1 это последняя функция, все вроде бы работает нормально. А если как в диалоге Dlg2, где после вызова onNext2, а значит и удаления объекта, есть ещё какой-то код получаю ""First-chance exception".
_>Собственно вопрос, это нормально и можно ли в принципе удалять самого себя или я перемудрил?

Где-то перемудрил, похоже. То есть, удалять сам себя можно, но с предельной аккуратностью.

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



Если твои диалоги модальные, то нельзя просто так пристрелить диалог, пока живо его окно, а программа крутится в недрах модального цикла.
Там, скорее, должно быть примерно такое
StateInfo m_current_state, m_next_state;

.....

while(!IsStopped())
{
    // показываем текущий диалог
    CDialog* pDlg = NewDialogForCurrentState(); // создаём диалог нужного типа
    pDlg->DoModal(); // диалог в своих недрах изменяет m_next_state и выходит из модального цикла
    delete pDlg; // всю содержательную работу диалог выполнил ранее, скажем, в обработчике кнопки OK
    
    m_current_state = m_next_state;
}


Если же диалоги немодальные... Тогда нужно сделать процедуру переключения окна (убийства-создания) и определиться с тем, кто ответственнен за её исполнение.
CDialog* m_pCurrentDialog;

.....

void ChangeDialog(CDialog* pNewDialog)
{
    if(m_pCurrentDialog)
    {
        .....
        m_pCurrentDialog->DestroyWindow();
        delete m_pCurrentDialog; // в том случае, если в обработчике WM_DESTROY это не делается
    }
    m_pCurrentDialog = pNewDialog;
    if(m_pCurrentDialog)
    {
        m_pCurrentDialog->Create(TheParentWindow());
        .....
    }
}

Вот с ответственностью — это самое интересное. Если у тебя есть родительское окно, и оно в курсе про танцы с диалогами — пусть оно и отвечает за происходящее.
Тогда из обработчика события в диалоге нужно послать этому родительскому окну звоночек, чтобы оно сделало ChangeDialog.
И лучше, если это будет не синхронное SendMessage, а отложенное PostMessage: тогда обработчик спокойно завершится.
А ещё лучше, если не просто PostMessage — которое можно обработать, и будучи глубоко в модальных циклах, — а строго в главном цикле прокачки сообщений. Что-нибудь типа OnIdle().



Подводя итог: идея в том, чтобы
1) Снять с убиваемых объектов (с диалогов) ответственность за убивание. Они могут лишь известить хозяина о том, что "уже пора".
2) Сделать процедуру убивания асинхронной. Убивать не в момент возбуждения этого извещения, а именно в подходящий момент.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re: может ли класс удалить сам себя
От: Nik_1 Россия  
Дата: 10.09.09 18:44
Оценка:
я когда реализовывал этот патерн решил проблему при помощи умных указателей. Причем желательно чтоб указатель умел лочиться от удаление на время вызова по неу метода обьекта.
Re[2]: может ли класс удалить сам себя
От: win_ce  
Дата: 11.09.09 07:55
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Вот с ответственностью — это самое интересное. Если у тебя есть родительское окно, и оно в курсе про танцы с диалогами — пусть оно и отвечает за происходящее.

К>Тогда из обработчика события в диалоге нужно послать этому родительскому окну звоночек, чтобы оно сделало ChangeDialog.
К>И лучше, если это будет не синхронное SendMessage, а отложенное PostMessage: тогда обработчик спокойно завершится.
К>А ещё лучше, если не просто PostMessage — которое можно обработать, и будучи глубоко в модальных циклах, — а строго в главном цикле прокачки сообщений. Что-нибудь типа OnIdle().

К>

К>Подводя итог: идея в том, чтобы
К>1) Снять с убиваемых объектов (с диалогов) ответственность за убивание. Они могут лишь известить хозяина о том, что "уже пора".
К>2) Сделать процедуру убивания асинхронной. Убивать не в момент возбуждения этого извещения, а именно в подходящий момент.





А можно развить тему с асинхронным удалением? Реальная картина такая — у CMainFrame есть переменная типа CChildView которая переключает наборы видов (состояний), т.е. логически связанные диалоги, переключение которых хотелось бы хранить только в одном классе, производном от ViewState, т.е. View1 хранит логику переключений между диалогами Dlg1, Dlg2, Dlg3 и т.д., View2 соответственно между Dlg4, Dlg5 и например Dlg2 ну так далее.


class CMyApp : public CWinApp
{
    BOOL InitInstance()
    {
        CMainFrame *pFrame = new CMainFrame;
        m_pMainWnd = pFrame;
        pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, NULL);
        pFrame->ShowWindow(m_nCmdShow);
        pFrame->UpdateWindow();
    }
};

class CMainFrame : public CFrameWnd
{
    CChildView m_wndView;

    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
        m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL);
    }
};

class CChildView : public CWnd
{
    ViewState *view_state_;

    void SwitchView1()
    {
        delete view_state_;
        view_state_ = new View1(this);
    }

    void SwitchView2()
    {
        delete view_state_;
        view_state_ = new View2(this);
    }

    ... и.т.д.
};


Соответственно ViewState реализует методы создания и удаления диалогов, а так же содержит указатель на CChildView через который производные классы могут переключаться между видами (состояниями).


class ViewState
{
    CChildView *parent_;
    CDialog *dialog_;

    ViewState(CChildView *parent_) : parent_(parent_) {}

    void createDialog(const unsigned int idd, CDialog *dlg)
    {
        closeDialog();
        dialog_ = dlg;
        dialog_->Create(idd, parent_);
        dialog_->ShowWindow(SW_SHOW);
    }

    void closeDialog()
    {
        dialog_->DestroyWindow();
        delete dialog_;
        dialog_ = NULL;
    }
};


Каждый конкретный вид содержит обработчики от всех диалогов и реализует логику переключений.


class View1 : public ViewState
{
    View1(CChildView *parent_) : ViewState(parent_)
    {
        createDlg1();        
    }

    void createDlg1()
    {
        createDialog(IDD_DIALOG_1, new Dlg1(this));
    }

    void createDlg2()
    {
        createDialog(IDD_DIALOG_2, new Dlg2(this));
    }

    void onNext1()
    {
        createDlg2();
    }

    void onNext2()
    {
        createDlg1();
    }

    void onCancel2()
    {
        parent_->SwitchView2();
    }
}


Диалоги вызывают методы вида, т.е. вызов Dlg1::onNext() приводит к тому, что объект Dlg1 будет удален, а объект Dlg2 создан.


class Dlg1 : public CDialog
{
    ViewState *state_;
    Dlg1(ViewState *state) : state_(state),  {}

    void onNext()
    {
        ((View1*)state_)->onNext1();
    }
}

class Dlg2 : public CDialog
{
    ViewState *state_;
    Dlg1(ViewState *state) : state_(state),  {}

    void onNext()
    {
        ((View1*)state_)->onNext2();
    }

    void onCancel()
    {
        ((View1*)state_)->onCancel2();
    }
}


Если я правильно понимаю, в методах типа View1::onNext1() вместо непосредственного удаления старого и создания нового диалога, предлагается отправить сообщение родительскому окну CChildView, чтобы он выполнил удаление/создание, верно? Но тогда получается, что CChildView должен знать все про производные диалоги, а это кажется не очень правильным. Или я что-то напутал?
Re[3]: может ли класс удалить сам себя
От: Кодт Россия  
Дата: 11.09.09 12:41
Оценка:
Здравствуйте, win_ce, Вы писали:

_>Если я правильно понимаю, в методах типа View1::onNext1() вместо непосредственного удаления старого и создания нового диалога, предлагается отправить сообщение родительскому окну CChildView, чтобы он выполнил удаление/создание, верно? Но тогда получается, что CChildView должен знать все про производные диалоги, а это кажется не очень правильным. Или я что-то напутал?


Пусть про производные диалоги знает ViewState. А CChildView только прокачивает сообщение.

struct IHolder
{
    virtual void commitChanges() = 0;
};
struct IPump
{
    virtual void notifyChanges(IHolder*) = 0;
};

/////////////////////////////

template<class Subordinate>
struct Holder : IHolder
{
    IPump* m_pump;
    Subordinate m_current, m_next;
    
    void startChanges(Subordinate next)
    {
        m_next = next;
        m_pump->notifyChanges(this);
    }
    void commitChanges()
    {
        goodbye();
        m_current = m_next;
        welcome();
    }
    
    virtual void goodbye() {} // код очистки текущего состояния
    virtual void welcome() {} // код инициализации
};

template<class TheHolder>
struct TypedPump : IPump
{
    virtual void notifyChanges(IHolder* holder) override { notifyChangesOf( static_cast<TheHolder*>(holder)); }
    virtual void notifyChangesOf(TheHolder* holder) = 0;
};

где ViewState — это Holder, а CChildView — соответственно, IPump.

Диалог, кстати, может и должен знать свой IDD в момент конструирования — незачем указывать его извне (только верёвочки и костыли плодить).
class CModelessDialog : public CDialog
{
public:
    CModelessDialog(UINT idd, CWnd* parent) : CDialog(idd, parent) {}
    BOOL Create() { return CDialog::Create(m_lpszTemplateName, m_pParentWnd); }
};
template<class Dlg> class CModelessDialogImpl
{
public:
    CModelessDialogImpl(CWnd* parent) : CModelessDialog(Dlg::IDD, parent) {}
};


Ну и теперь несложно всё это свалить в кучу
class ViewState : Holder<CModelessDialog*>
{
    ViewState(CChildView* view) : Holder(view) {}

    virtual void goodbye() override
    {
        if(!m_current) return;
        m_current->DestroyWindow();
        delete m_current;
        m_current = NULL;
    }
    virtual void welcome() override
    {
        if(!m_current) return;
        m_current->Create();
    }
    
    virtual void init() = 0; // потомок должен создать первоначальный диалог нужного типа
};

class CChildView : public Holder<ViewState>, public TypedPump<ViewState>, public TypedPump<CChildView>
{
    .....
    virtual void notifyChangesOf(ViewState* state) override { this->PostMessage(WM_TOUCH_STATE,0,0); }
    virtual void notifyChangesOf(CChildView* myself) override { this->PostMessage(WM_CHANGE_STATE,0,0); }
    
    void onTouchState() { m_current->commitChanges(); }
    void onChangeState() { this->commitChanges(); }
    
    void goodbye() override { delete m_current; m_current = NULL; }
    void welcome() override { if(m_current) m_current->init(); }
};
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
Re[4]: может ли класс удалить сам себя
От: win_ce  
Дата: 14.09.09 08:34
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Пусть про производные диалоги знает ViewState. А CChildView только прокачивает сообщение.


К>Ну и теперь несложно всё это свалить в кучу


Спасибо большое за код, но я честно говоря не могу разобраться как это работатет. Зачем CChildView реализует оба интерфейса IHolder и Ipump? В конечном итоге запрос на удаление диалога и создание нового идет от какого-то конкретного диалога:

class Dlg1 : public CModelessDialog
{
    void onNext()
    {
       // как нам тут послать сообщение на удаление и как гарантировать, что диалог будет удален после выхода из метода?
    }
};
Re[5]: может ли класс удалить сам себя
От: Кодт Россия  
Дата: 14.09.09 12:20
Оценка:
Здравствуйте, win_ce, Вы писали:

_>Спасибо большое за код, но я честно говоря не могу разобраться как это работатет.


_>Зачем CChildView реализует оба интерфейса IHolder и Ipump?


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

IHolder — потому что он является владельцем сменяемых ViewState.
Было бы очень естественно сделать замену ViewState таким же способом: инициировал — выждал подходящий момент — заменил.

_> В конечном итоге запрос на удаление диалога и создание нового идет от какого-то конкретного диалога:


_>
_>class Dlg1 : public CModelessDialog
_>{
_>    void onNext()
_>    {
_>       // как нам тут послать сообщение на удаление и как гарантировать, что диалог будет удален после выхода из метода?
_>    }
_>};
_>


Сообщение (точнее, вызов метода) посылается в адрес ViewState, который владеет этим диалогом. Т.е. this->state_->onNext1(), например.
Диалог будет удалён ПОСЛЕ выхода из метода — благодаря тому, что ViewState создаёт отложенный вызов своего метода commitChanges().

Подобно тому, как обычный модальный диалог закрывается:
1) где-нибудь, скажем, в OnOK вызывается функция EndDialog(IDOK);
2) эта функция взводит специальный флажок в данных окна (SetWindowLong(DWL_DLGRESULT), кажется? не дебажил, не уверен);
3) цикл прокачки сообщений в недрах DoModal (::DialogBox) проверяет этот флажок, и если он взведён, — убивает окно и выходит из функции

У нас история похожая, только цикл прокачки сообщений — не модальный, а основной.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.