Приемы применения статических членов — разделяемый ресурс



Статические переменные-члены

 

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

 

На практике встречается необходимость в переменных-членах иного рода — относящихся к классу в целом, а не к конкретному объекту. Такие переменные-члены называются СТАТИЧЕСКИМИ. Соответственно, обычные переменные-члены, относящиеся к объектам, иногда называют НЕСТАТИЧЕСКИМИ. Статические и нестатические переменные-члены объединяет общая область видимости, одинаковое влияние спецификаторов доступа (public, private,..).

 

Однако время жизни статических переменных-членов принципиально отличается от нестатических. Местом хранения статических переменных-членов в памяти является сегмент данных, что соответствует глобальным переменным. Отсюда вытекает идентичное глобальным переменным время жизни — они создаются до начала выполнения функции main() и уничтожаются после ее завершения.

 

Сколько бы ни было создано объектов класса, статическая переменная-член существует в памяти строго в единственном экземпляре. Этот экземпляр существует даже если не было фактически создано ни одного объекта класса. Очевидно, наличие статических переменных-членов в классе никаким образом не увеличивает размеры объектов.

 

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

 

class Test

{

// Статическая переменная-член (храним независимо от объектов в сегменте данных)

static int ms_ObjCounter;

 

// Обычная (нестатическая) переменная-член (храним в объекте)

int m_Data;

   

public:

   

// Конструктор

Test ( int _data )

   : m_Data( _data )

{

   // Увеличиваем счетчик, хранящийся в статической переменной-члене

   ms_ObjCounter++;

}

   

// Конструктор копий

Test ( const Test & _test )

   : m_Data( _test.m_Data )

{

   // Также увеличиваем счетчик

   ms_ObjCounter++;

}

};

 

Из внутренних функций-членов класса обращение к статическим переменным-членам непосредственно по имени ничем не отличается от обращения к обычным переменным-членам.

 

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

 

● подобно нестатическим членам, через любой из объектов класса + точка, либо через указатель на объект класса + стрелка:

 

Test t;

std::cout << t.ms_ObjCounter;

 

Test * pTest = & t;

std::cout << pTest->ms_objCounter;

 

● через имя класса (только для статических членов!):

   

std::cout << Test::ms_objCounter;

 

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

 

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

 

int Test::ms_ObjCounter;

 

Ключевое слово static в определении повторно использовать не нужно.

 

Как и для глобальных переменных, статическим членам присваивается нулевое значение по умолчанию. Если тип статического члена сам является структурой или классом, вызывается конструктор по умолчанию. Переменные встроенных типов автоматически получат нулевое значение. При необходимости, определение может определять собственное значение, если оно должно отличаться от автоматически присваиваемого:

 

int Test::ms_ObjCounter = 5;

 

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

 

error LNK2001: unresolved external symbol "private: static int Test::ms_ObjCounter" (?ms_ObjCounter@Test@@0HA)

 

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

 

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

 

Особый случай статических переменных-членов представляют собой константы целочисленных типов (char, short, int, long, а также перечисления). Подобно глобальным константам, такие статические константные члены подпадают под правила внутренней компоновки (internal linkage). Соответственно, их можно инициализировать сразу в заголовочной части класса, поскольку при таком типе компоновки допускается наличие нескольких копий одного и того же определения в различных единицах трансляции. При помещении в заголовочную часть класса, предполагается, что инициализирующее выражение будет возможно вычислить во время компиляции. В приведенном ниже примере константа целочисленного типа получает значение сразу при объявлении, и используется для задания размера массива в объектах:

 

class Queue

{

   static const int QUEUE_LENGTH = 100;

   int m_QueueData[ QUEUE_LENGTH ];

};

   

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

 

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

 

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

 

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

 

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

 

Из отсутствия неявно передаваемого указателя this в статических функциях-членах вытекает ряд ограничений на содержащийся в их телах программный код:

1. Нельзя напрямую по имени обращаться к нестатическим переменным-членам класса, поскольку нет объекта, через который можно было бы получить к ним доступ.

2. Нельзя напрямую по имени вызывать нестатические функции-члены, поскольку нет объекта, указатель на который передавать в качестве this при вызове.

3. Нельзя совмещать модификаторы static и const, поскольку модификатор const для функций-членов влияет на тип неявно передаваемого указателя this, которого статические функции-члены не получают.

 

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

 

В приведенном ниже примере используются как статическая переменная-член, так и статическая функция-член (по сути, функция играет роль статического варианта метода-геттера). Имеется класс, описывающий оценку студента по некоторой дисциплине. В статической переменной-члене ms_MaxMark сохраняется максимальная оценка, которую получал любой из экземпляров, автоматически корректируемая в конструкторе объектов. А при помощи статической функции-члена GetMaxMark определенный на текущий момент максимум оценки возвращается клиентскому коду:

 

student.hpp

   

#ifndef _STUDENT_HPP_

#define _STUDENT_HPP_

 

class Student

{

// Обычные (нестатические) переменные-члены (храним в объекте)

const char * m_LastName;

const int m_Mark;

 

// Статическая переменная-член (храним независимо от объектов в сегменте данных).

// Содержит текущую накопленную максимальную оценку студентов.

static int ms_MaxMark;

 

public:

 

// Объявление конструктора

Student ( const char * _lastName, int _mark );

 

// Статическая функция-член: возвращает текущую накопленную максимальную оценку

static int GetMaxMark ();

 

};

 

// Реализация статической функции-члена.

// Модификатор static в выносной реализации не указывается.

inline int Student::GetMaxMark ()

{

return ms_MaxMark; // Доступ к этой переменной разрешен.

                   // Обращаться к m_LastName или m_Mark здесь нельзя

}

 

#endif // _STUDENT_HPP_

 

 

student.cpp

 

#include “student.hpp”

 

// Определение статической переменной-члена, значение 0 по умолчанию

int Student::ms_MaxMark;

 

// Реализация конструктора

Student::Student ( const char * _lastName, int _mark )

: m_LastName( _lastName ),

    m_Mark( _mark )

{

// Обновляем максимальную оценку, если новая оценка выше

if ( m_Mark > ms_MaxMark )

   // Конструктор является обычной нестатической функцией-членом)

   // Соответственно, может получать доступ к любой переменной класса.

   ms_MaxMark = m_Mark;

                            

}

 

 

test.cpp

 

#include “student.hpp”

#include <iostream>

 

int main ()

{

// Создаем несколько экземпляров класса с различными оценками

Student s1( "Ivanov", 75 );

Student s2( "Petrov", 80 );

Student s3( "Sidorov", 60 );

 

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

std::cout << "Maximum mark among students: "

         << Student::GetMaxMark() // this здесь не передается

         << std::endl;

 

// Вызов через любой объект также возможен, tnis не передается

// ... << s1.GetMaxMark() ... <<

}

 

Вывод программы на консоль будет содержать следующее:

 

Maximum mark among students: 80

 

Приемы применения статических членов — разделяемый ресурс

 

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

 

Деструктор файлового потока вызывается автоматически при выходе из функции main(), что освободит ресурсы операционной системы, связанные с открытым программой файлом.

 

student.hpp

   

#ifndef _STUDENT_HPP_

#define _STUDENT_HPP_

 

#include <iostream>

 

class Student

{

// Обычные переменные-члены

const char * m_LastName;

int m_Mark;

 

// Статическая переменная-член. Поток трассировочного файла

static std::fstream ms_DumpFile;

 

public:

 

// Конструктор

Student ( const char * _lastName, int _mark );

 

// Функция генерации отладочной трассировки для объекта

void Dump ();

};

 

#endif // _STUDENT_HPP_

 

 

student.cpp

 

#include “student.hpp”

 

// Определение статической переменной-члена, конструктор по умолчанию

std::fstream Student::ms_DumpFile;

 

// Реализация конструктора

Student::Student ( const char * _lastName, int _mark )

: m_LastName( _lastName ), m_Mark( _mark )

{

}

 

// Реализация функции генерации отладочной трассировки

void Student::Dump ()

{

// Если общий трассировочный файл еще не открыт, пора его открывать

if ( ! ms_DumpFile.is_open() )

{

   // Открываем файл в режиме записи

   ms_DumpFile.open( “students.txt”, std::ios_base::out );

}

 

// Отладочная трасса объекта

ms_DumpFile << "Student " << m_LastName << " - " << m_Mark << std::endl;

}

 

 

test.cpp

 

#include “student.hpp”

 

int main ()

{

Student s1( "Ivanov", 75 );

Student s2( "Petrov", 80 );

Student s3( "Sidorov", 60 );

 

s1.Dump();

s2.Dump();

s3.Dump();

}

 

В трассировочном файле после выполнения программы будет находиться следующее содержимое:

 

Student Ivanov – 75

Student Petrov – 80

Student Sidorov – 60

 


Дата добавления: 2021-01-21; просмотров: 60; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!