Общие данные классов - статические и защищенные!
От: Anrie Nord  
Дата: 18.11.01 14:11
Оценка:
Еще раз всем привет!

Прежде всего, большое спасибо тебе, IT, за ответ на прошлое сообщение. Действительно, как и было видно из текста ошибки, экземпляр переменной не создавался. Но я пытался это сделать. В частности, последний мой примерчик — он был как раз про это. Просто я уже настолько привык, что переменные-члены инициализируются в конструкторах, что только так и пробовал.

В книге я прочитал пример, в котором статическая переменная была public. Вот ее как раз и определяли вне класса, как ты. Но я просто не догадался, что и private-перменные тоже можно определять вне класса. Так что действительно все работает, права доступа не нарушаются!

Чтобы подвести итог, расскажу, зачем все это надо было и приведу пример.

Дело было в следующем. Надо написать wizard на C++ с использованием WTL. За каждый отдельный экран визарда отвечает класс, производный от WTL-класса CPropertyPageImpl<>. И поскольку визард должен хранить данные, введенные пользователем на каждом экране, то нужна была структура для хранения данных всех классов, производных от данного.

Первое, что пришло в голову, — глобальная структура. Но данные не защищены от внешних посягательств. Второй способ я узал от Алекса Федотова. Его идея состояла в том, чтобы сделать структуру локальной, объявленной в блоке использования страниц визарда. Но ее пришлось таскать по конструкторам каждой страницы. Еще одним, довольно существенным минусом была невозможность использовать переменные с общими данными для прямой связи с контролами механизмом DDX. Вот пример моего кода на основе этой мысли:

void f()
{
    // shared data
    CWizCtx ctx;

    // create property pages
    CWelcomePage page0(&ctx);
    CStartupPage page1(&ctx);
    
    /* ... */
}


Теперь же можно запихать все объявления и определения классов экранов визарда а один заголовочный файл, а когда понадобятся данные, полученные от визарда, надо будет всего лишь создать объект класса данных визарда — и все готово! И даже прямая связь переменных-членов CWizardData со всеми контролами визарда! =) Вот окончательный пример:

Файл "wizard.h":
// класс для хранения общих данных
class CWizardData {
// защищаемся от непотомков, наконец-то!
protected:
    static int ddxTask;
    /* ... */
};

// вот она, глобальная инициализация защищенной переменной! =)
// ddxTask - связана с переключателями, определяет
//      вид задачи, выполняемой юзером
// -1 - не выбран ни один radio button
int CWizardData::ddxTask = -1;

// класс, описывающий все страницы визарда, уже
//      включает функциональность общего хранилища
template<typename T>
class CWizardPage : public CPropertyPageImpl<T>,
                    public CWizardData {
    /* ... */
};

// класс одного из экранов
class CStartupPage : public CWizardPage<CStartupPage>,
                     public CWinDataExchange<CStartupPage> {
    /* ... */
public:
    // карта связей DDX
    BEGIN_DDX_MAP(CStartupPage)
        DDX_RADIO(IDC_RADIO_0, CWizardData::ddxTask)
    END_DDX_MAP()
    
    // один из обработчков сообщений
    LRESULT OnClickedRadio(/* ... */)
    {
        SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT);
        // запись в общее хранилище, а ведь даже не заметна
        //      и не требует никакого дополнительного кода!
        DoDataExchange(DDX_SAVE, IDC_RADIO_PLAYLIST_LOAD);
        
        // а это - чтение оттуда же
        if (CWizardData::ddxTask == 0) {
        // сделать что-то
        }
        return 0;
    }
};


Файл "usage.cpp":
// диалоговый класс, тоже порождается от CWizardData,
//      таким образом только классы экранов визарда и
//      класс, его запускающий, могут использовать данные
class CMainDlg : public CDialogImpl<CMainDlg>,
                 public CWizardData {
    /* ... */
public:
    // а вот и метод, в котором вызывается визард
    LRESULT OnOK(WORD /* ... */)
    {
        // создаем экраны визарда
        CWelcomePage page0();
        CStartupPage page1();
    
        // создаем объект-визард
        CPropertySheet sheet;
    
        // добавляем в него странички
        sheet.AddPage(page0.Create());
        sheet.AddPage(page1.Create());
    
        // тут ставим всякие флажки визарда

        // отображем визард
        sheet.DoModal();
        
        // а вот и доступ к данным визарда
        //      я использую спецификатор CWizard::
        //      для того, чтобы не забыть о
        //      статичности переменной
        switch (CWizard::ddxData) {
            /* ... */
        }   
            
        return 0;
    }


Все, чего хотелось, получилось! Все, кроме одного. Если объявлять класс CWizardData в заголовочном файле и там же инициализировать переменные, то можно использовать этот заголовочный файл только один раз! Иначе будет ошибка линкера о повторном определении переменной в объектном файле:

second.obj : error LNK2005: "protected: static
int  CWizardData::ddxTask" (?ddxTask@CWizardData@@1HA)
already defined in usage.obj


Так что, хоть и с ограничениями, но с победой =) Может вы знаете, как преодолеть такое ограничение на единичное включение?

P.S.
2 Sashko: Твое замечание очень справедливо, но ко мне оно не относится: я изучаю английский с 5 лет. А вот всем остальным я тоже скажу: учите английский! =)


--
Всем всего хорошего,
Анри Норд
Re: Общие данные классов - статические и защищенные!
От: paul_shmakov Россия  
Дата: 18.11.01 14:38
Оценка:
Здравствуйте Anrie Nord, Вы писали:

Просто вынести строчку описания статической переменной в отдельный файл:

wizard.cpp
#include "wizard.h"

int CWizardData::ddxTask = -1;
Paul Shmakov
Re: Общие данные классов - статические и защищенные!
От: Alexander Shargin Россия RSDN.ru
Дата: 18.11.01 19:50
Оценка: 2 (1)
Здравствуйте Anrie Nord, Вы писали:

AN>Все, чего хотелось, получилось! Все, кроме одного. Если объявлять класс CWizardData в заголовочном файле и там же инициализировать переменные, то можно использовать этот заголовочный файл только один раз! Иначе будет ошибка линкера о повторном определении переменной в объектном файле:


AN>
AN>second.obj : error LNK2005: "protected: static
AN>int  CWizardData::ddxTask" (?ddxTask@CWizardData@@1HA)
AN>already defined in usage.obj
AN>


AN>Так что, хоть и с ограничениями, но с победой =) Может вы знаете, как преодолеть такое ограничение на единичное включение?


Да, авторы ATL уже давно всё придумали. Посмотри, например, на этот макрос из ATL:

#define DECLARE_WND_CLASS(WndClassName) static CWndClassInfo& GetWndClassInfo() {     static CWndClassInfo wc =     {         { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc,           0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL },         NULL, NULL, IDC_ARROW, TRUE, 0, _T("")     };     return wc; }


Как видишь, структура CWndClassInfo объявляется не как статическая переменная класса, а как статическая переменная статической функции GetWndClassInfo, которая возвращает ссылку на неё. Именно для того, чтобы статическую переменную не надо было нигде описывать. Остроумный ход!

В случае с твоей переменной int можно сделать то же самое:

class MyClass
{
protected:
 static int &GetInt()
 {
  static int intVar = -1;
  return intVar;
 }
 ...
};


И далее работать с переменной через MyClass::GetInt(). Благодаря этому не надо заводить отдельный cpp-файл и описывать там переменную, всё будет находиться в одном месте в заголовочном файле.
--
Я думал, ты огромный страшный Бажище,
А ты недоучка, крохотный Бажик...
Re: Общие данные классов - статические и защищенные!
От: Snax Россия  
Дата: 19.11.01 03:06
Оценка:
Здравствуйте Anrie Nord, Вы писали:

AN>В книге я прочитал пример, в котором статическая переменная была public. Вот ее как раз и определяли вне класса, как ты. Но я просто не догадался, что и private-перменные тоже можно определять вне класса. Так что действительно все работает, права доступа не нарушаются!


не можно а нужно все статические переменные определять вне класса.

AN>2 Sashko: Твое замечание очень справедливо, но ко мне оно не относится: я изучаю английский с 5 лет. А вот всем остальным я тоже скажу: учите английский! =)


AN>Всем всего хорошего,

AN>Анри Норд

Не в обиду будет сказано, но я хотел бы сюда еще добавить

... и старайтесь думать головой!

Потому что если линкер говорит error LNK2005, это значит, что
произошла ошибка LNK2005, и прежде чем бежать на RSDN отвлекать серьезных
людей, можно просто нажать Секретную Хакерскую Кнопку и получить ответ
не отходя от кассы.

Еще раз прошу прощения за грубый тон,
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.