13.5. Статические члены класса

We use cookies. Read the Privacy and Cookie Policy

13.5. Статические члены класса

Иногда нужно, чтобы все объекты некоторого класса имели доступ к единственному глобальному объекту. Допустим, необходимо подсчитать, сколько их было создано; глобальным может быть указатель на процедуру обработки ошибок для класса или, скажем, указатель на свободную память для его объектов. В подобных случаях более эффективно иметь один глобальный объект, используемый всеми объектами класса, чем отдельные члены в каждом объекте. Хотя такой объект является глобальным, он существует лишь для поддержки реализации абстракции класса.

В этой ситуации приемлемым решением является статический член класса, который ведет себя как глобальный объект, принадлежащий своему классу. В отличие от других членов, которые присутствуют в каждом объекте как отдельные элементы данных, статический член существует в единственном экземпляре и связан с самим типом, а не с конкретным его объектом. Это разделяемая сущность, доступная всем объектам одного класса.

По сравнению с глобальным объектом у статического члена есть следующие преимущества:

* статический член не находится в глобальном пространстве имен программы, следовательно, уменьшается вероятность случайного конфликта имен с другими глобальными объектами;

* остается возможность сокрытия информации, так как статический член может быть закрытым, а глобальный объект – никогда.

Чтобы сделать член статическим, надо поместить в начале его объявления в теле класса ключевое слово 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"

double Account::_interestRate = 0.0589;

В программе может быть только одно определение статического члена. Это означает, что инициализацию таких членов следует помещать не в заголовочные файлы, а туда, где находятся определения невстроенных функций-членов класса.

В объявлении статического члена можно указать любой тип. Это могут быть константные объекты, массивы, объекты классов и т.д. Например:

#include string

class Account {

// ...

private:

static const string name;

};

const string Account::name( "Savings Account" );

Константный статический член целого типа инициализируется константой внутри тела класса: это особый случай. Если бы для хранения названия счета мы решили использовать массив символов вместо строки, то его размер можно было бы задать с помощью константного члена типа 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.

Поскольку есть лишь одна копия статического члена класса, до нее необязательно добираться через объект или указатель. Другой способ заключается в том, чтобы обратиться к статическому члену напрямую, квалифицировав его имя именем класса:

// доступ к статическому члену с указанием квалифицированного имени

if ( Account::_interestRate 0.05 )

Если обращение к статическому члену производится без помощи оператора доступа, то его имя следует квалифицировать именем класса, за которым следует оператор разрешения области видимости:

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 );

};

13.5.1. Статические функции-члены

Функции-члены raiseInterest() и interest() обращаются к глобальному статическому члену _interestRate:

class Account {

public:

void raiseInterest( double incr );

double interest() { return _interestRate; }

private:

static double _interestRate;

};

inline void Account::raiseInterest( double incr )

{

_interestRate += incr;

}

Проблема в том, что любая функция-член должна вызываться с помощью оператора доступа к конкретному объекту класса. Поскольку приведенные выше функции обращаются только к статическому _interestRate, то совершенно безразлично, для какого объекта они вызываются. Нестатические члены при вызове этих функций не читаются и не модифицируются.

Поэтому лучше объявить такие функции-члены как статические. Это можно сделать следующим образом:

class Account {

public:

static void raiseInterest( double incr );

static double interest() { return _interestRate; }

private:

static double _interestRate;

};

inline void Account::raiseInterest( double incr )

{

_interestRate += incr;

}

Объявление статической функции-члена почти такое же, как и нестатической: в теле класса ему предшествует ключевое слово static, а спецификаторы const или volatile запрещены. В ее определении, находящемся вне тела класса, слова static быть не должно.

Такой функции-члену указатель this не передается, поэтому явное или неявное обращение к нему внутри ее тела вызывает ошибку компиляции. В частности, попытка обращения к нестатическому члену класса неявно требует наличия указателя this и, следовательно, запрещена. Например, представленную ранее функцию-член dailyReturn() нельзя объявить статической, поскольку она обращается к нестатическому члену _amount.

Статическую функцию-член можно вызвать для объекта класса, пользуясь одним из операторов доступа. Ее также можно вызвать непосредственно, квалифицировав ее имя, даже если никаких объектов класса не объявлено. Вот небольшая программа, иллюстрирующая их применение:

#include iostream

#include "account.h"

bool limitTest( double limit )

{

// пока еще ни одного объекта класса Account не объявлено

// правильно: вызов статической функции-члена

return limit = Account::interest() ;

}

int main() {

double limit = 0.05;

if ( limitTest( limit ) )

{

// указатель на статическую функцию-член

// объявлен как обычный указатель

void (*psf)(double) = &Account::raiseInterest;

psf( 0.0025 );

}

Account ac1( 5000, "Asterix" );

Account ac2( 10000, "Obelix" );

if ( compareRevenue( ac1, &ac2 ) & 0 )

cout ac1.owner()

"is richer than "

ac2.owner() " ";

else

cout ac1.owner()

" is poorer than"

ac2.owner() " ";

return 0;

}

Упражнение 13.8

Пусть дан класс Y с двумя статическими данными-членами и двумя статическими функциями-членами:

class X {

public:

X( int i ) { _val = i; }

int val() { return _val; }

private:

int _val;

};

class Y {

public:

Y( int i );

static X xval();

static int callsXval();

private:

static X _xval;

static int _callsXval;

};

Инициализируйте _xval значением 20, а _callsXval значением 0.

Упражнение 13.9

Используя классы из упражнения 13.8, реализуйте обе статические функции-члена для класса Y. callsXval() должна подсчитывать, сколько раз вызывалась xval().

Упражнение 13.10

Какие из следующих объявлений и определений статических членов ошибочны? Почему?

// example.h

class Example {

public:

static double rate = 6.5;

static const int vecSize = 20;

static vectordouble vec(vecSize);

};

// example.c

#include "example.h "

double Example::rate;

vectordouble Example::vec;