C++. Классы памяти, которые могут объявляться при работе с классами и объектами классов. Ключевые слова register, extern, static, mutable

C++. Классы памяти, которые могут объявляться при работе с классами и объектами классов. Ключевые слова register, extern, static, mutable


Содержание


1. С какими классами памяти могут объявляться объекты классов и классы?

Объекты классов и классы могут объявляться с такими классами памяти:

  • автоматический (auto);
  • регистровый (register);
  • внешний (extern);
  • статический (static).
  • изменчивый (mutable).

Для каждого класса памяти определяется собственный спецификатор: auto, register, extern, static. Спецификаторы классов памяти определяют, как должна храниться переменная.

2. Каким образом используется класс памяти auto для классов? Пример

Класс памяти auto определяет автоматические переменные. По умолчанию, это локальные переменные, которые размещаются в стеке или внутренних регистрах процессора. «Время жизни» автоматической переменной ограничено временем выполнения функции (метода) или блока, в котором эта переменная определена. При обычном объявлении локального объекта класса или локальной переменной, например

int a;
double x;
char c;
CMyClass MC; // объект класса

эта переменная получает класс памяти auto.

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

Пример. Пусть имеем фрагмент функции, которая вычисляет сумму элементов массива A, состоящего из n целых чисел.

int MySum(int A[], int n)
{
    // объявление автоматической переменной sum
    int sum; // начало "жизни" или существование переменной sum

    sum = 0;
    for (int i=0; i<n; i++) // цикл, который содержит блок { ... }
    {
        // начало существования автоматической переменной i
        sum = sum + A[i]; 
        // конец существования автоматической переменной i
    }

    return sum;
    // конец существования автоматической переменной sum
}

В функции MySum есть следующие автоматические переменные:

  • A, n – формальные параметры являются также автоматическими переменными;
  • sum, i – внутренние локальные переменные функции MySum.

Это также касается и объявления объекта класса. Если в методе (функции) объявлен объект класса, то этот объект имеет класс памяти auto.

Например. Пусть задан некоторый класс CMyClass.

class CMyClass
{
    int d;

    public:
    // конструктор
    CMyClass(void) { d = 0; };

    // методы класса
    int Get(void) { return d; }
    void Set(int nd) { d = nd; }
};

Тогда, в некоторой функции MyFun() объект класса будет относиться к классу памяти auto (автоматический).

void MyFun(...)
{
    CMyClass MC; // объявление объекта класса с классом памяти auto
    ...
}

3. Каким образом используется класс памяти register? Пример

Объект класса может объявляться с ключевым словом register. Задание ключевого слова register – это есть указание компилятору выделить для сохранения данных объекта не ячейке стека, а внутренние регистры процессора. Но это не означает, что компилятор обязательно разместит данные объекта в регистрах процессора. Кроме того, при указании register, компилятор может разместить данные в кэш-памяти. Основной целью объявления переменной (объекта) с ключевым словом register есть обеспечение максимально быстрого доступа к этой переменной и обработки этой переменной.

Пример. Пусть имеем объявление класса CMyClass.

class CMyClass
{
    int d;

    public:
    // конструктор
    CMyClass(void);

    // методы класса
    int Get(void) { return d; }
    void Set(int nd) { d = nd; }
};

// конструктор класса
CMyClass::CMyClass(void)
{
    d = 0;
}

Объект класса – это переменная типа этого класса. Поэтому, объект как и переменная может иметь класс памяти register.

// объявление объекта класса с классом памяти register
register CMyClass MC1;

int t;
t = MC1.Get(); // t = 0

MC1.Set(15);
t = MC1.Get(); // t = 15

4. Каким образом используется класс памяти extern?

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



Например. Пусть дано два модуля «MyClass.h» и «Main.h». В модуле «MyClass.h» реализовано объявление класса CMyClass. В модуле «Main.h» реализовано использование класса «MyClass.h», который подключается извне с помощью директивы #include.

Чтобы использовать возможности внешнего класса CMyClass, в тексте модуля «Main.h» может быть добавлен следующий код

#include "MyClass.h"
extern class CMyClass; // объявление, что класс CMyClass есть внешним

В этом случае, класс CMyClass считается внешним, то есть таким, который имеет класс памяти extern.

5. Каким образом используются статические объекты класса (static)?

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

Более подробно об использовании статических переменных в классах описывается в статье:

Пример. Пусть задан класс CMyCounter.

class CMyCounter
{
    public:
    int d;
    CMyCounter(void) { d = 0; }; // конструктор класса
    ~CMyCounter(void) {}; // деструктор класса
};

В классе объявлена общедоступная переменная d, конструктор и деструктор. Конструктор класса обнуляет начальное значение d.

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

// функция, в которой объявлен статический объект класса
int IncMyCounter(void)
{
    // объявление объекта класса с классом памяти static
    static CMyCounter MC; // объект размещается в фиксированной области памяти
    MC.d = MC.d + 1; // увеличение на 1
    return MC.d;
}

Использование функции IncMyCounter() из другого программного кода демонстрирует тот факт, что статический объект не уничтожается после завершения вызова функции

...

int t;
t = IncMyCounter(); // t = 1
t = IncMyCounter(); // t = 2 - учтено предыдущее значение CMyCounter::d
t = IncMyCounter(); // t = 3 - учтено предыдущее значение CMyCounter::d

...

Как видно из вышеприведенного фрагмента кода функция вызывается 3 раза. В последних двух вызовах учитывается предыдущее значение переменной d класса CMyCounter.

Если в функции IncMyCounter() объявить объект MC класса CMyCounter без ключевого слова static

void IncMyCounter(void)
{
    // объявление объекта класса без ключевого слова static
    CMyCounter MC; // память выделена в стеке, память временная (нефиксированная)
    MC.d = MC.d + 1; // увеличение на 1
}

это этот объект будет относиться к автоматическому (auto) классу памяти. В этом случае, при каждом вызове функции IncMyCounter()

...

int t;
t = IncMyCounter(); // t = 1
t = IncMyCounter(); // t = 1 - предшествующее значение CMyCounter::d не учитывается
t = IncMyCounter(); // t = 1 - предшествующее значение CMyCounter::d не учитывается

...

для внутренней переменной d объекта класса CMyCounter, память будет снова выделяться. В результате чего, переменная d объекта MC будет:

  • создаваться заново в нефиксированной области памяти при ее объявлении;
  • инициализироваться с помощью конструктора класса (d =0);
  • уничтожаться при выходе из функции IncMyCounter(), поскольку объект класса MC объявлен как автоматический (класс памяти auto).

6. В каких случаях используется класс памяти mutable (непостоянный) для объекта класса? Пример

Класс памяти mutable используется в объединении с так называемыми константными функциями (функциями, которые объявлены с ключевым словом const).

Пример функции с модификатором const в классе CMyRadius

class CMyRadius
{
    double r; // радиус объекта

    public:
    // константная функция - вычисляет площадь круга - можно
    double GetSquare(void) const
    {
        return (double)(3.1415 * r * r);
    }
}

Если константная функция объявлена в классе, то эта функция не может изменять данные класса. Это есть основной целью объявления константной функции в классе – не допустить модификацию объекта, который ее вызовет. То есть, нижеследующую функцию в классе CMyRadius нельзя реализовывать:

// константная функция - устанавливает новое r - запрещено
void SetR(double nr) const
{
    r = nr;
}

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

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

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

mutable double r; // радиус объекта

Такая запись означает, что значение переменной r в классе можно изменять с помощью константной функции. То есть, константную функцию Set() можно объявить в классе.

После изменений, текст класса следующий:

class CMyRadius
{
    mutable double r; // радиус объекта - объявлен как mutable

    public:
    // константная функция - вычисляет площадь круга - можно
    double GetSquare(void) const
    {
        return (double)(3.1415 * r * r);
    }

    // константная функция - устанавливает новое r - можно
    void SetR(double nr) const
    {
        r = nr;
    }
}

Использование класса в другом программном коде

CMyRadiusMR;
int t;

MR.Set(2);
t = MR.GetSquare(); // t = 12.566

Вышеприведенные утверждения справедливы только для native-классов. В среде CLR в managed-классах использование квалификаторов const и volatile не поддерживается.


Связанные темы