Правила программирования на Си и Си++

       

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


Как правило, переменная должна инициализироваться во время объявления. Разделение инициализации и объявления иногда обусловливается плохим проектированием в программе, которая написана не вами, как в следующем фрагменте, написанном для выполнения совместно с библиотекой 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: первая попытка
class window : public CWnd
{
public:


    class caption
    {
       CWnd *target_window;
    private: friend class
window;
       caption( CWnd *p ) : target_window(p) {}

     public:
        operator CString ( void ) const;
        const caption operator=( const CString s );
     };

     caption text( void );
  };
  //–-------------------------------------------------------
  caption window::text( void )
  {
     return caption( this );
  }
  //--------------------------------------------------------
  window::caption::operator CString( void ) const
  {
     CString output;
     target_window-GetWindowText( output );
     return output;                 // возвращает копию
  }
  //--------------------------------------------------------
  const caption window::caption::operator=(const
CString s)
  {
     target_window-SetWindowText( s );
     return *this;
  }

Листинг 12. Заголовочный объект
class caption
{
    CWnd target_window;
public:
    window_text( CWnd *win ) : target_window( win ) {};
    operator const CString( void );
    const CString operator=( const CString r );
};

  inline caption::operator CString( void
);
  {
     CString output;
     target_window-GetWindowText( output );
         return output;
  }

  inline const
CString caption::operator=(const
CString s )
  {
    // возвращает тип CString (вместо типа заголовка
     // "caption"), поэтому будет срабатывать a = b = "абв"

     target_window-SetWindowText( s );
     return s;
  }
Часть 8д. Виртуальные функции
Виртуальные функции придают объекту производного класса способность модифицировать поведение, определенное на уровне базового класса (или предоставить какие-то возможности, в которых базовый класс испытывал потребность, но не мог их реализовать обычно из-за того, что информация, нужная для этой реализации, объявляется на уровне производного класса). Виртуальные функции являются центральными для объектно-ориентированного проектирования, потому что они позволяют вам определить базовый класс общего назначения, не требуя знания особенностей, которые могут быть предусмотрены лишь производным классом. Вы можете писать программу, которая думает, что манипулирует объектами базового класса, но на самом деле во время выполнения воздействует на объекты производного класса. Например, вы можете написать код, помещающий объект в обобщенную структуру данных data_structure, но на самом деле во время выполнения он вставляет его в tree или linked_list (классы, производные от data_structure). Это настолько фундаментальная объектно-ориентированная операция, что программа на Си++, которая не использует виртуальные функции, вероятно, просто плохо спроектирована.

Содержание раздела