Правила программирования на С и С++. Главы 7-8
Страница 47. Избегайте инициализации в два приема


134. Избегайте инициализации в два приема.

135. Суперобложки на С++ для существующих интерфейсов редко хорошо работают.

Как правило, переменная должна инициализироваться во время объявления. Разделение инициализации и объявления иногда обусловливается плохим проектированием в программе, которая написана не вами, как в следующем фрагменте, написанном для выполнения совместно с библиотекой MFC Microsoft:

f( CWnd *win ) // CWnd - это окно

{

// Следующая строка загружает "буфер" с шапкой окна

// (текстом в строке заголовка)

char buf[80]; /* = */ win->GetWindowText(buf, sizeof(buf));

// ...

}Так как я должен выполнить инициализацию при помощи явного вызова функции, то умышленно нарушаю свое правило "один оператор в строке" для того, чтобы по крайней мере вместить объявление и инициализацию в одной и той же строке.

Здесь имеется несколько проблем, первая из которых заключается в плохом проектировании класса CWnd (представляющем окно). Так как у окна есть "тестовый" атрибут, хранящий заголовок, то вы должны иметь возможность доступа к этому атрибуту подобным образом:

CString caption = win->caption();и вы должны иметь возможность модифицировать этот атрибут так: win->caption() = "новое содержание";но вы не можете сделать этого в текущей реализации. Главная проблема состоит в том, библиотека MFC не была спро

ектирована в объектно-ориентированном духе - т.е. начать с объектов, затем выбрать, какие сообщения передавать между ними и какими атрибутами их наделить. Вместо этого проектировщики Microsoft начали от существующего процедурного интерфейса (API С - интерфейса прикладного программирования для Windows на С) и добавили к нему суперобложку на С++, тем самым увековечив все проблемы существующего интерфейса. Так как в API С была функция с именем GetWindowText(), то проектировщики беззаботно сымитировали такой вызов при помощи функции-члена в своей оболочке CWnd. Они поставили заплату на интерфейс при помощи следующего вызова:

CString str;

win->GetWindowText( str );

но это - не решение по двум причинам: по-прежнему требуется инициализация в два приема, и аргумент является ссылкой на результат.

Главный урок состоит в том, что проекты, основанные на процедурном подходе, радикально отличаются от объектно-ориентированных проектов. Обычно невозможно использовать код из одного проекта в другом без большой переработки. Простая оболочка из классов С++ вокруг процедурного проекта не сделает его объектно-ориентированным.

Поучительно, я думаю, пошарить вокруг в поисках решения текущей проблемы с помощью С++, но предупреждаю вас - здесь нет хорошего решения (кроме перепроектирования библиотеки классов). Моя первая попытка сделать оболочку вокруг CWnd показана на листинге 11.

Для обеспечения возможности win->text() = "Новый заголовок" необходим вспомогательный класс (window::caption). Вызов text() возвращает объект заголовка, которому затем передается сообщение присваиванием.

Главная проблема на листинге 11 заключается в том, что библиотека MFC имеет много классов, унаследованных от CWnd, и интерфейс, реализованный в классе window, не будет отражен в других потомках CWnd. С++ является компилируемым языком, поэтому нет возможности вставлять класс в средину иерархии классов без изменения исходного кода.

Листинг 12 определяет другое решение для смеси С++ с MFC. Я выделил класс window::caption в отдельный класс, который присоединяется к окну, когда оно инициализируется. Используется подобным образом:

f(CWnd *win)

{

caption cap( win )

CString s = cap; // поддерживается преобразование в CString.

cap = "Новый заголовок"; // использует операцию operator=(CString?)

}Мне не нравится то, что изменение заголовка caption меняет также окно, к которому этот заголовок присоединен в этом последнем примере. Скрытая связь между двумя объектами может сама по себе быть источником недоразумений, будучи слишком похожей на побочный эффект макроса. Как бы то ни было, листинг 12 решает проблему инициализации.

Листинг 11. Обертка для CWnd: первая попытка.

  1. class window : public CWnd
  2. {
  3. public:
  4. class caption
  5. {
  6. CWnd *target_window;
  7. private: friend class window;
  8. caption( CWnd *p ) : target_window(p) {}
  9. public:
  10. operator CString ( void ) const;
  11. const caption ?operator=( const CString ?s );
  12. };
  13. caption text( void );
  14. };
  15. //--------------------------------------------------------------
  16. caption window::text( void )
  17. {
  18. return caption( this );
  19. }
  20. //--------------------------------------------------------------
  21. window::caption::operator CString( void ) const
  22. {
  23. CString output;
  24. target_window->GetWindowText( output );
  25. return output; // возвращает копию
  26. }
  27. //--------------------------------------------------------------
  28. const caption ?window::caption::operation=( const CString ?s )
  29. {
  30. target_window->SetWindowText( s );
  31. return *this;
  32. }
Листинг 12. Заголовочный объект
  1. class caption
  2. {
  3. CWnd target_window;
  4. public:
  5. window_text( CWnd *win ) : target_window( win ) {};
  6. operator const CString( void );
  7. const CString ?operator=( const CString ?r );
  8. };
  9. inline caption::operator CString( void );
  10. {
  11. CString output;
  12. target_window->GetWindowText( output );
  13. return output;
  14. }
  15. inline const CString ?caption::operator= ( const CString ?s )
  16. {
  17. // возвращает тип CString (вместо типа заголовка "caption"),
  18. // поэтому будет срабатывать
  19. // a = b = "абв"
  20. target_window->SetWindowText( s );
  21. return s;
  22. }

 
« Предыдущая статья   Следующая статья »