Статические члены класса
Иногда нужно, чтобы все объекты некоторого класса имели доступ к единственному глобальному объекту. Допустим, необходимо подсчитать, сколько их было создано; глобальным может быть указатель на процедуру обработки ошибок для класса или, скажем, указатель на свободную память для его объектов. В подобных случаях более эффективно иметь один глобальный объект, используемый всеми объектами класса, чем отдельные члены в каждом объекте. Хотя такой объект является глобальным, он существует лишь для поддержки реализации абстракции класса.
В этой ситуации приемлемым решением является статический член класса, который ведет себя как глобальный объект, принадлежащий своему классу. В отличие от других членов, которые присутствуют в каждом объекте как отдельные элементы данных, статический член существует в единственном экземпляре и связан с самим типом, а не с конкретным его объектом. Это разделяемая сущность, доступная всем объектам одного класса.
По сравнению с глобальным объектом у статического члена есть следующие преимущества:
·
статический член не находится в глобальном пространстве имен программы, следовательно, уменьшается вероятность случайного конфликта имен с другими глобальными объектами;
· остается возможность сокрытия информации, так как статический член может быть закрытым, а глобальный объект – никогда.
Чтобы сделать член статическим, надо поместить в начале его объявления в теле класса ключевое слово static. К ним применимы все правила доступа к открытым, закрытым и защищенным членам. Например, для определенного ниже класса Account член _interestRate
объявлен как закрытый и статический типа double:
class Account { // расчетный счет Account( double amount, const string &owner ); string owner() { return _owner; } private: static double _interestRate; // процентная ставка double _amount; // сумма на счету string _owner; // владелец |
};
Почему _interestRate
сделан статическим, а _amount и _owner
нет? Потому что у всех счетов разные владельцы и суммы, но процентная ставка одинакова. Следовательно, объявление члена _interestRate статическим уменьшает объем памяти, необходимый для хранения объекта Account.
Хотя текущее значение _interestRate для всех счетов одинаково, но со временем оно может изменяться. Поэтому мы решили не объявлять этот член как const. Достаточно модифицировать его лишь один раз, и с этого момента все объекты Account будут видеть новое значение. Если бы у каждого объекта была собственная копия, то пришлось бы обновить их все, что неэффективно и является потенциальным источником ошибок.
В общем случае статический член инициализируется вне определения класса. Его имя во внешнем определении должно быть специфицировано именем класса. Вот так можно инициализировать _interestRate:
// явная инициализация статического члена класса #include "account.h" |
В программе может быть только одно определение статического члена. Это означает, что инициализацию таких членов следует помещать не в заголовочные файлы, а туда, где находятся определения невстроенных функций-членов класса.
В объявлении статического члена можно указать любой тип. Это могут быть константные объекты, массивы, объекты классов и т.д. Например:
#include <string> class Account { // ... private: static const string name; }; |
Константный статический член целого типа инициализируется константой внутри тела класса: это особый случай. Если бы для хранения названия счета мы решили использовать массив символов вместо строки, то его размер можно было бы задать с помощью константного члена типа int:
// заголовочный файл class Account { //... private: static const int nameSize = 16; static const string name[nameSize]; }; // исходный файл const string Account::nameSize; // необходимо определение члена |
const string Account::name[nameSize] = "Savings Account";
Отметим, что константный статический член целого типа, инициализированный константой, – это константное выражение. Проектировщик может объявить такой статический член, если внутри тела класса возникает необходимость в именованной константе. Например, поскольку константный статический член nameSize является константным выражением, проектировщик использует его для задания размера члена-массива с именем name.
Даже если такой член инициализируется в теле класса, его все равно необходимо задать вне определения класса. Однако поскольку начальное значение уже задано в объявлении, то при определении оно не указывается.
Так как name – это массив (и не целого типа), его нельзя инициализировать в теле класса. Попытка поступить таким образом приведет к ошибке компиляции:
class Account { //... private: static const int nameSize = 16; // правильно: целый тип static const string name[nameSize] = "Savings Account"; // ошибка |
Член name
должен быть инициализирован вне определения класса.
Обратите внимание, что член nameSize
задает размер массива name в определении, находящемся вне тела класса:
const string Account::name[nameSize] = "Savings Account";
nameSize не квалифицирован именем класса Account. И хотя это закрытый член, определение name не приводит к ошибке. Как такое может быть? Определение статического члена аналогично определению функции-члена класса, которое может ссылаться на закрытые члены. Определение статического члена name
находится в области видимости класса и может ссылаться на закрытые члены, после того как распознано квалифицированное имя Account::name. (Подробнее об области видимости класса мы поговорим в разделе 13.9.)
Статический член класса доступен функции-члену того же класса и без использования соответствующих операторов:
inline double Account::dailyReturn() { return( _interestRate / 365 * _amount ); |
Что же касается функций, не являющихся членами класса, то они могут обращаться к статическому члену двумя способами. Во-первых, посредством операторов доступа:
class Account { // ... private: friend int compareRevenue( Account&, Account* ); // остальное без изменения }; // мы используем ссылочный и указательный параметры, // чтобы проиллюстрировать оба оператора доступа int compareRevenue( Account &ac1, Account *ac2 ); { double ret1, ret2; ret1 = ac1._interestRate * ac1._amount; ret2 = ac2->_interestRate * ac2->_amount; // ... |
Как ac1._interestRate, так и ac2->_interestRate
относятся к статическому члену Account::_interestRate.
Поскольку есть лишь одна копия статического члена класса, до нее необязательно добираться через объект или указатель. Другой способ заключается в том, чтобы обратиться к статическому члену напрямую, квалифицировав его имя именем класса:
// доступ к статическому члену с указанием квалифицированного имени |
Если обращение к статическому члену производится без помощи оператора доступа, то его имя следует квалифицировать именем класса, за которым следует оператор разрешения области видимости:
Account::
Это необходимо, поскольку такой член не является глобальным объектом, а значит, в глобальной области видимости отсутствует. Следующее определение дружественной функции compareRevenue
эквивалентно приведенному выше:
int compareRevenue( Account &ac1, Account *ac2 ); { double ret1, ret2; ret1 = Account::_interestRate * ac1._amount; ret2 = Account::_interestRate * ac2->_amount; // ... |
Уникальная особенность статического члена – то, что он существует независимо от объектов класса, – позволяет использовать его такими способами, которые для нестатических членов недопустимы.
· статический член может принадлежать к типу того же класса, членом которого он является. Нестатические объявляются лишь как указатели или ссылки на объект своего класса:
class Bar { public: // ... private: static Bar mem1; // правильно Bar *mem2; // правильно Bar mem3; // ошибка |
};
· статический член может выступать в роли аргумента по умолчанию для функции-члена класса, а для нестатического это запрещено:
extern int var; class Foo { private: int var; static int stcvar; public: // ошибка: трактуется как Foo::var, // но ассоциированного объекта класса не существует int mem1( int = var ); // правильно: трактуется как static Foo::stcvar, // ассоциированный объект и не нужен int mem2( int = stcvar ); // правильно: трактуется как глобальная переменная var int mem3( int = :: var ); |