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 є забезпечення максимально швидкого доступу до цієї змінної та обробки цієї змінної.

Приклад. Нехай маємо оголошення класу 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 в класі можна змінювати з допомогою константної функції. Тобто, константну функцію SetR() можна оголосити в класі.

Після змін, текст класу наступний:

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.SetR(2);
t = MR.GetSquare(); // t = 12.566

Вищенаведені твердження справедливі тільки для native-класів. У середовищі CLR в managed-класах використання кваліфікаторів const та volatile не підтримується.


Зв’язані теми