Здравствуйте 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-файл и описывать там переменную, всё будет находиться в одном месте в заголовочном файле.
Еще раз всем привет!
Прежде всего, большое спасибо тебе, 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 лет. А вот всем остальным я тоже скажу: учите английский! =)
--
Всем всего хорошего,
Анри Норд
Здравствуйте Anrie Nord, Вы писали:
Просто вынести строчку описания статической переменной в отдельный файл:
wizard.cpp
#include "wizard.h"
int CWizardData::ddxTask = -1;
Здравствуйте Anrie Nord, Вы писали:
AN>В книге я прочитал пример, в котором статическая переменная была public. Вот ее как раз и определяли вне класса, как ты. Но я просто не догадался, что и private-перменные тоже можно определять вне класса. Так что действительно все работает, права доступа не нарушаются!
не
можно а
нужно все статические переменные определять
вне класса.
AN>2 Sashko: Твое замечание очень справедливо, но ко мне оно не относится: я изучаю английский с 5 лет. А вот всем остальным я тоже скажу: учите английский! =)
AN>Всем всего хорошего,
AN>Анри Норд
Не в обиду будет сказано, но я хотел бы сюда еще добавить
... и старайтесь думать головой!
Потому что если линкер говорит
error LNK2005, это значит, что
произошла ошибка LNK2005, и прежде чем бежать на RSDN отвлекать серьезных
людей, можно просто нажать Секретную Хакерскую Кнопку и получить ответ
не отходя от кассы.
Еще раз прошу прощения за грубый тон,