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

       

Не портьте область глобальных имен: проблемы Си++


Определение класса обеспечивает отличный способ вывода идентификатора из области глобальных имен, потому что эти идентификаторы должны быть доступны или через объект, или посредством явного имени класса. Функция x.f()

отличается от y.f(), если x и y

являются объектами разных классов. Аналогично, x::f()

отличается от y::f(). Вы должны смотреть на имя класса и :: как эффективную часть имени функции, которая может быть опущена лишь тогда, когда что-нибудь еще (типа . или -) служит для уточнения.

Я часто использую перечислитель для ограничения видимости идентификатора константы областью видимости класса:

class tree

{

   enum { max_nodes = 128 };

public:

   enum traversal_mechanism { inorder, preorder, postorder };

   print( traversal_mechanism how = inorder );

   // ...

}



// ...

f()

{

   tree t;

   // ...

   t.print( tree::postorder );

}

Константа tree::postorder, переданная в функцию print(), определенно не в глобальной области имен, потому что для доступа к ней требуется префикс tree::. При этом не возникает конфликта имен, так как если другой класс имеет член с именем postorder, то он вне класса будет именоваться other_class::postorder. Более того, константа max_nodes

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

Преимущество перечислителя над членом-константой класса состоит в том, что его значение может быть инициализировано прямо в объявлении класса. Член-константа должен инициализироваться в функции-конструкторе, который может быть в другом файле. Перечислитель может быть также использован в качестве размера в объявлении массива и в качестве значения case в операторе switch; константа ни в одном из этих мест работать не будет§.

Константа-член имеет свое предназначение. Во-первых, вы можете помещать в нее значения с типом, отличным от int. Во-вторых, вы можете инициализировать ее во время выполнения. Рассмотрим следующее определение глобальной переменной в Си++:


const int
default_size = get_default_size_from_ini_file();
Ее значение считывается из файла во время загрузки программы, и оно не может быть изменено во время выполнения.
Вышеупомянутое также применимо к константам-членам класса, которые могут быть инициализированы через аргумент конструктора, но не могут меняться функциями-членами. Так как объект типа const не может стоять слева от знака равенства, константы-члены должны инициализироваться посредством списка инициализации членов следующим образом:
class fixed_size_window
{
   const size height;
   const size width;
   fixed_size_window( size the_height, size the_width )
                                 : height( the_height )
                                 , width ( the_width )
   {}
}
Вложенные классы также полезны. Вам часто будет нужно создать "вспомогательный" класс, о котором ваш пользователь даже не будет знать. Например, текст программы из Листинга 10 реализует класс int_array —
динамический двухмерный массив, размер которого может быть неизвестен до времени выполнения. Вы можете получить доступ к его элементам, используя стандартный для Си/Си++ синтаксис массива (a[row][col]). Класс int_array
делает это, используя вспомогательный класс, о котором пользователь int_array
ничего не знает. Я использовал вложенное определение для удаления определения этого вспомогательного класса из области видимости глобальных имен. Вот как это работает: Выражение a[row][col]
оценивается как (a[row])[col]. a[row]
вызывает int_array::operator[](),
который возвращает объект int_array::row, ссылающийся на целую строку. [col]
применяется к этому объекту int_array::row, приводя к вызову int_array::row::operator[](). Эта вторая версия operator[]()
возвращает ссылку на индивидуальную ячейку. Заметьте, что конструктор класса int_array::row
является закрытым, потому что я не хочу, чтобы любой пользователь имел возможность создать строку row. Строка должна предоставить дружественный статус массиву int_array с тем, чтобы int_array мог ее создать.


Листинг 10. Вспомогательные классы
#include iostream.h
class int_array
{
    class row
    {
       friend class
int_array;
       int *first_cell_in_row;
       row( int *p ) : first_cell_in_row(p) {}
    public:
       int operator[] ( int index );
    };

    int nrows;
    int ncols;
    int *the_array;

public:
    virtual
       ~int_array( void           );
        int_array( int rows, int
cols );

        row operator[] (int index);
};
//========================================================
// функции-члены класса int_array
//========================================================
int_array::int_array( int rows, int cols )
                         : nrows ( rows )
                         , ncols ( cols )
                         , the_array ( new int[rows * cols])
{}
//--------------------------------------------------------
int_array::~int_array( void )
{
    delete [] the_array;
}
//--------------------------------------------------------
inline int_array::row int_array::operator[]( int index )
{
    return row( the_array + (ncols * index) );
}
//========================================================
// функции-члены класса int_array::row
//========================================================
inline int
int_array::row::operator[]( int index )
{
    return first_cell_in_row[ index ];
}

//========================================================
void main ( void )      // ..§
{
    int_array ar(10,20); //
то же самое, что и ar[10][20], но
                         // размерность
во время компиляции
    ar[1][2] = 100;      // может быть не определена.
    cout ar[1][2];
}

Часть 8в. Ссылки

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