Приведення типів, explicit та delete
Приведення типів, explicit та delete
Невидимі конвертації: коли компілятор занадто «розумний»
У попередніх статтях ми навчили наші класи працювати з операторами: додавання, множення, порівняння. Код виглядає природно і читабельно. Але C++ має ще одну потужну — і водночас небезпечну — можливість: автоматичне приведення типів (implicit type conversion).
Розглянемо просту ситуацію:
#include <iostream>
class Dollars
{
private:
int amount;
public:
Dollars(int a = 0) : amount(a) {}
int getAmount() const { return amount; }
void print() const
{
std::cout << "$" << amount;
}
};
void printMoney(Dollars money)
{
money.print();
std::cout << "\n";
}
int main()
{
Dollars wallet(50);
printMoney(wallet); // ✅ Очевидно: передаємо об'єкт Dollars
// ❓ А це?
printMoney(100); // ✅ Компілюється! Чому?
printMoney(3.14); // ✅ Теж працює! double → int → Dollars
return 0;
}
Що відбулося? На рядку 32 компілятор бачить printMoney(100) і розуміє: функція очікує Dollars, але отримує int. Компілятор знаходить конструктор Dollars(int a) і автоматично створює тимчасовий об'єкт Dollars(100). Це працює, але чи завжди це бажано?
Рядок 33: 3.14 → спочатку double перетворюється на int (стандартна конвертація), потім int перетворюється на Dollars (користувацька конвертація). Втрачається частина числа.
Такі неявні конвертації можуть призвести до тонких помилок, коли компілятор «здогадується» неправильно, і код компілюється без попереджень, але працює не так, як очікувалося.
У цій статті ми навчимося контролювати ці конвертації: дозволяти лише явні, забороняти небезпечні, створювати власні оператори приведення типів.
- Implicit conversion (неявна конвертація) — компілятор автоматично перетворює один тип на інший
- Explicit conversion (явна конвертація) — програміст явно вказує приведення типу через
static_castабо викликає конструктор - User-defined conversion (користувацька конвертація) — конвертація, визначена програмістом через конструктори або оператори приведення
Конструктори як оператори конвертації
Будь-який конструктор, що приймає один аргумент, компілятор розглядає як оператор неявної конвертації. Це означає, що коли компілятор бачить невідповідність типів, він автоматично викликає такий конструктор.
#include <iostream>
class Fraction
{
private:
int numerator;
int denominator;
public:
// Конструктор із одним параметром — оператор конвертації int → Fraction
Fraction(int num) : numerator(num), denominator(1)
{
std::cout << "Converting " << num << " to Fraction\n";
}
Fraction(int num, int den) : numerator(num), denominator(den) {}
void print() const
{
std::cout << numerator << "/" << denominator;
}
};
void printFraction(Fraction f)
{
f.print();
std::cout << "\n";
}
int main()
{
Fraction f1(3, 4);
printFraction(f1); // ✅ Прямий виклик
// Неявна конвертація int → Fraction
printFraction(5); // ⚠️ Створюється Fraction(5, 1)
// Неявна конвертація у виразах
Fraction f2 = 7; // ⚠️ Еквівалентно Fraction f2(7);
return 0;
}
Рядок 11: конструктор Fraction(int num) приймає один параметр. Компілятор дозволяє використовувати його для автоматичної конвертації int → Fraction.
Рядок 36: printFraction(5) — компілятор бачить невідповідність: функція очікує Fraction, але отримує int. Компілятор викликає Fraction(5), створює тимчасовий об'єкт Fraction(5, 1), передає його у функцію.
Рядок 39: Fraction f2 = 7 — не викликається operator=, бо об'єкт ще не існує. Це ініціалізація, що еквівалентна Fraction f2(7). Компілятор викликає конвертуючий конструктор.
void processAccount(BankAccount acc). Якщо у класі BankAccount є конструктор BankAccount(int accountNumber), то виклик processAccount(12345) компілюється без помилок. Але програміст міг мати на увазі щось інше (наприклад, передати суму), і код працює некоректно. Компілятор не попередить.Ключове слово explicit: заборона неявних конвертацій
Щоб заборонити компілятору автоматично викликати конструктор для конвертації, використовуємо ключове слово explicit. Воно дозволяє лише явні виклики конструктора.
#include <iostream>
class Fraction
{
private:
int numerator;
int denominator;
public:
// explicit забороняє неявну конвертацію int → Fraction
explicit Fraction(int num) : numerator(num), denominator(1)
{
std::cout << "Explicit constructor called\n";
}
Fraction(int num, int den) : numerator(num), denominator(den) {}
void print() const
{
std::cout << numerator << "/" << denominator;
}
};
void printFraction(Fraction f)
{
f.print();
std::cout << "\n";
}
int main()
{
Fraction f1(3, 4);
printFraction(f1); // ✅ Працює
// ❌ Неявна конвертація заборонена
// printFraction(5); // ПОМИЛКА КОМПІЛЯЦІЇ!
// ✅ Явна конвертація дозволена
printFraction(Fraction(5)); // Явний виклик конструктора
printFraction(static_cast<Fraction>(7)); // Явне приведення
// ❌ Неявна ініціалізація заборонена
// Fraction f2 = 9; // ПОМИЛКА КОМПІЛЯЦІЇ!
// ✅ Явна ініціалізація дозволена
Fraction f3(9); // Пряма ініціалізація
Fraction f4{11}; // Uniform initialization
return 0;
}
Рядок 11: ключове слово explicit перед конструктором забороняє неявні конвертації. Компілятор не дозволить автоматичний виклик цього конструктора.
Рядок 36 (закоментований): printFraction(5) тепер помилка компіляції. Компілятор не може автоматично створити Fraction із int.
Рядок 39–40: явний виклик конструктора Fraction(5) або явне приведення static_cast<Fraction>(7) дозволені. Програміст явно вказує намір.
Рядок 43 (закоментований): Fraction f2 = 9 — помилка компіляції. Синтаксис із = інтерпретується як неявна конвертація.
Рядки 46–47: пряма ініціалізація Fraction f3(9) або uniform initialization Fraction f4{11} працюють, бо це явні виклики конструктора.
explicit, якщо неявна конвертація не є очевидною і бажаною. Це захищає від випадкових помилок. Виключення: типи-обгортки на зразок std::string, де std::string s = "hello"; є природним.Оператори приведення типів: user-defined conversions
Окрім конвертації в наш клас (через конструктори), C++ дозволяє визначити конвертацію з нашого класу в інші типи. Це робиться через оператори приведення типів (conversion operators).
Синтаксис:
operator ТипПризначення() const
{
// Конвертуємо поточний об'єкт у ТипПризначення
return значення;
}
Особливості:
- Немає типу повернення (тип вказано в імені оператора)
- Зазвичай
const-метод (не змінює об'єкт) - Компілятор викликає автоматично при невідповідності типів
#include <iostream>
class Fraction
{
private:
int numerator;
int denominator;
public:
Fraction(int num, int den) : numerator(num), denominator(den) {}
// Оператор конвертації Fraction → double
operator double() const
{
return static_cast<double>(numerator) / denominator;
}
// Оператор конвертації Fraction → int (повертає цілу частину)
operator int() const
{
return numerator / denominator;
}
void print() const
{
std::cout << numerator << "/" << denominator;
}
};
void printDouble(double value)
{
std::cout << "Double: " << value << "\n";
}
void printInt(int value)
{
std::cout << "Int: " << value << "\n";
}
int main()
{
Fraction f(7, 2); // 7/2 = 3.5
std::cout << "Fraction: ";
f.print();
std::cout << "\n\n";
// Неявна конвертація Fraction → double
printDouble(f); // Викликається operator double()
// Неявна конвертація Fraction → int
printInt(f); // Викликається operator int()
// Явна конвертація через static_cast
double d = static_cast<double>(f);
int i = static_cast<int>(f);
std::cout << "\nExplicit conversions:\n";
std::cout << "Double: " << d << "\n";
std::cout << "Int: " << i << "\n";
// Неявна конвертація у виразах
double result = f + 1.5; // f → double, потім 3.5 + 1.5
std::cout << "\nf + 1.5 = " << result << "\n";
return 0;
}
Рядки 13–16: operator double() перетворює дріб на double, обчислюючи ділення numerator / denominator. Компілятор викликає цей оператор автоматично, коли очікується double, але передається Fraction.
Рядки 19–22: operator int() повертає цілу частину дробу (округлення вниз через цілочисельне ділення).
Рядок 51: printDouble(f) — компілятор бачить невідповідність типів: функція очікує double, але отримує Fraction. Компілятор знаходить operator double() і викликає його.
Рядок 66: вираз f + 1.5 — компілятор перетворює f на double (через operator double()), потім виконує 3.5 + 1.5 = 5.0.
operator int(), і operator double(), деякі вирази можуть бути неоднозначними. Наприклад, f + 1 — компілятор не знає, чи конвертувати f в int або double. Це викличе помилку компіляції: ambiguous overload. Щоб уникнути цього, використовуйте оператори приведення обережно.explicit для операторів приведення
Так само, як для конструкторів, ключове слово explicit можна застосовувати до операторів приведення типів. Це забороняє неявну конвертацію, дозволяючи лише явну.
#include <iostream>
class Fraction
{
private:
int numerator;
int denominator;
public:
Fraction(int num, int den) : numerator(num), denominator(den) {}
// explicit забороняє неявну конвертацію Fraction → double
explicit operator double() const
{
return static_cast<double>(numerator) / denominator;
}
void print() const
{
std::cout << numerator << "/" << denominator;
}
};
void printDouble(double value)
{
std::cout << "Double: " << value << "\n";
}
int main()
{
Fraction f(3, 4);
// ❌ Неявна конвертація заборонена
// printDouble(f); // ПОМИЛКА КОМПІЛЯЦІЇ!
// double d = f; // ПОМИЛКА КОМПІЛЯЦІЇ!
// double sum = f + 1.5; // ПОМИЛКА КОМПІЛЯЦІЇ!
// ✅ Явна конвертація дозволена
printDouble(static_cast<double>(f));
double d = static_cast<double>(f);
std::cout << "Explicit conversion: " << d << "\n";
return 0;
}
Рядок 13: explicit operator double() дозволяє конвертацію лише через явний static_cast або пряме приведення.
Рядки 34–36 (закоментовані): всі неявні конвертації тепер заборонені. Компілятор видасть помилку: cannot convert 'Fraction' to 'double' without a explicit conversion.
Рядки 39–40: явне приведення static_cast<double>(f) працює. Програміст явно вказує намір конвертувати.
Спеціальний випадок: explicit operator bool()
Один із найпоширеніших патернів — конвертація об'єкта в bool для використання в умовах (if, while). Наприклад, std::ifstream можна перевіряти: if (file) { ... }.
Але operator bool() без explicit може призвести до дуже дивних помилок:
#include <iostream>
class Pointer
{
private:
int* ptr;
public:
Pointer(int* p = nullptr) : ptr(p) {}
// НЕ explicit — небезпечно!
operator bool() const
{
return ptr != nullptr;
}
};
int main()
{
Pointer p1(new int(10));
Pointer p2(nullptr);
// ✅ Передбачувана поведінка
if (p1)
{
std::cout << "p1 is not null\n";
}
// ❌ Несподівана поведінка: компілюється, але помилка!
int sum = p1 + p2; // p1 → bool → int (1), p2 → bool → int (0), sum = 1
std::cout << "sum = " << sum << "\n"; // sum = 1 — що???
// ❌ Ще гірше: можна порівнювати з числами!
if (p1 == 1) // p1 → bool → int, порівнюємо int == int
{
std::cout << "p1 == 1 ?!\n";
}
return 0;
}
Рядок 31: p1 + p2 — компілятор перетворює p1 і p2 на bool (через operator bool()), потім bool на int (стандартна конвертація true → 1, false → 0), потім додає їх. Результат: 1 + 0 = 1. Це абсурд, але компілюється без помилок!
Рядок 35: p1 == 1 — компілятор перетворює p1 на bool, потім на int, порівнює 1 == 1. Працює, але семантично неправильно.
Рішення: використовуйте explicit operator bool():
#include <iostream>
class Pointer
{
private:
int* ptr;
public:
Pointer(int* p = nullptr) : ptr(p) {}
// explicit — безпечно!
explicit operator bool() const
{
return ptr != nullptr;
}
};
int main()
{
Pointer p1(new int(10));
Pointer p2(nullptr);
// ✅ Використання в умовах працює
if (p1)
{
std::cout << "p1 is not null\n";
}
// ❌ Арифметичні операції заборонені
// int sum = p1 + p2; // ПОМИЛКА КОМПІЛЯЦІЇ!
// ❌ Порівняння з числами заборонені
// if (p1 == 1) {} // ПОМИЛКА КОМПІЛЯЦІЇ!
return 0;
}
Рядок 12: explicit operator bool() дозволяє використання в контекстах, де очікується bool (умови if, while, !, &&, ||), але забороняє неявну конвертацію в інших контекстах.
Рядок 24: if (p1) працює, бо в умові if очікується bool, і компілятор дозволяє explicit конвертацію.
Рядки 31, 34 (закоментовані): арифметичні операції та порівняння з числами тепер заборонені. Компілятор видає помилку.
operator bool():ЗАВЖДИ робіть operator bool() з explicit. Це стандартна практика у сучасному C++. Стандартна бібліотека (std::ifstream, std::unique_ptr, std::optional) використовує саме explicit operator bool().Ключове слово delete: заборона небажаних операцій
C++11 додав ключове слово = delete, що дозволяє явно заборонити виклик функції, конструктора або оператора. Це корисно для запобігання небажаним конвертаціям або операціям.
Заборона конструкторів-конвертерів
#include <iostream>
class MyString
{
private:
char* data;
public:
MyString(const char* str)
{
// Копіюємо рядок
int length = 0;
while (str[length] != '\0') ++length;
data = new char[length + 1];
for (int i = 0; i <= length; ++i)
{
data[i] = str[i];
}
std::cout << "MyString constructed from const char*\n";
}
// Забороняємо конструктор із char — захист від помилок
MyString(char) = delete;
~MyString()
{
delete[] data;
}
};
int main()
{
MyString s1("Hello"); // ✅ Працює
// ❌ Заборонено — захищає від помилки
// MyString s2('x'); // ПОМИЛКА КОМПІЛЯЦІЇ!
// MyString s3 = 'A'; // ПОМИЛКА КОМПІЛЯЦІЇ!
return 0;
}
Рядок 23: MyString(char) = delete; забороняє створення MyString із одного символу char. Без цього рядка MyString s('x') компілювався б: компілятор перетворював би char на const char* (адреса символу), що призвело б до невизначеної поведінки.
Рядки 36–37 (закоментовані): спроби створити MyString із char тепер викликають помилку компіляції: use of deleted function.
Заборона небажаних перевантажень операторів
= delete також використовується для заборони певних перевантажень операторів, щоб запобігти небезпечним або безглуздим операціям.
#include <iostream>
class SafeInt
{
private:
int value;
public:
SafeInt(int v) : value(v) {}
int getValue() const { return value; }
// Дозволяємо порівняння з іншим SafeInt
bool operator==(const SafeInt& other) const
{
return value == other.value;
}
// Забороняємо порівняння з double — може призвести до помилок точності
bool operator==(double) const = delete;
// Забороняємо ділення — щоб уникнути цілочисельного ділення без контролю
SafeInt operator/(const SafeInt&) const = delete;
};
int main()
{
SafeInt a(10);
SafeInt b(10);
SafeInt c(5);
// ✅ Порівняння між SafeInt працює
if (a == b)
{
std::cout << "a == b\n";
}
// ❌ Порівняння з double заборонено
// if (a == 10.0) {} // ПОМИЛКА КОМПІЛЯЦІЇ!
// ❌ Ділення заборонено
// SafeInt result = a / c; // ПОМИЛКА КОМПІЛЯЦІЇ!
return 0;
}
Рядок 20: bool operator==(double) const = delete; забороняє порівняння SafeInt із double. Це захищає від помилок точності плаваючої коми: 10 == 10.0000001 може дати неочікуваний результат.
Рядок 23: SafeInt operator/(const SafeInt&) const = delete; забороняє ділення між SafeInt. Це може бути корисно, якщо ви хочете примусити програміста явно обробляти цілочисельне ділення або використовувати інший метод.
Рядки 41, 44 (закоментовані): спроби використати заборонені операції викликають помилку компіляції з ясним повідомленням.
Заборона копіювання та присвоювання
Одне з найпоширеніших застосувань = delete — заборона копіювання об'єктів. Це корисно для класів, що керують унікальними ресурсами (файли, з'єднання, mutex).
#include <iostream>
class FileHandle
{
private:
int fileDescriptor;
public:
FileHandle(const char* filename)
{
// Симуляція відкриття файлу
fileDescriptor = 42; // Фіктивний дескриптор
std::cout << "File opened: " << filename << "\n";
}
~FileHandle()
{
std::cout << "File closed\n";
}
// Забороняємо копіювання — файл не можна дублювати
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
void write(const char* data)
{
std::cout << "Writing: " << data << "\n";
}
};
int main()
{
FileHandle file("data.txt");
file.write("Hello, World!");
// ❌ Копіювання заборонено
// FileHandle file2 = file; // ПОМИЛКА КОМПІЛЯЦІЇ!
// FileHandle file3(file); // ПОМИЛКА КОМПІЛЯЦІЇ!
// ❌ Присвоювання заборонено
// FileHandle file4("other.txt");
// file4 = file; // ПОМИЛКА КОМПІЛЯЦІЇ!
return 0;
}
Рядки 22–23: забороняємо конструктор копіювання та оператор присвоювання. Це гарантує, що об'єкт FileHandle існує лише в одній копії — не можна випадково продублювати файловий дескриптор, що призвело б до подвійного закриття.
Рядки 38–39, 43 (закоментовані): спроби копіювання або присвоювання викликають чітку помилку компіляції.
std::unique_ptr використовує саме цей патерн — копіювання заборонено через = delete, дозволено лише переміщення (move). Це гарантує, що покажчик завжди унікальний.Порівняння: до C++11 vs після C++11
До C++11 для заборони копіювання використовували приватні конструктор копіювання та оператор присвоювання без реалізації:
class FileHandle
{
private:
// Оголошення без реалізації — помилка лінкування при спробі виклику
FileHandle(const FileHandle&);
FileHandle& operator=(const FileHandle&);
public:
FileHandle(const char* filename) { /* ... */ }
};
class FileHandle
{
public:
FileHandle(const char* filename) { /* ... */ }
// Явна заборона — помилка компіляції із зрозумілим повідомленням
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
};
Переваги = delete:
- Помилка виявляється на етапі компіляції, а не лінкування
- Чітке повідомлення про помилку: use of deleted function
- Явне документування наміру: код самодокументується
- Можна забороняти не лише конструктори, але й будь-які функції та оператори
= delete замість старого патерну з приватними методами. Це робить намір явним і покращує діагностику помилок.Практичний приклад: клас Money із захищеними конвертаціями
Об'єднаємо все, що ми вивчили, у безпечний клас Money із контрольованими конвертаціями:
#include <iostream>
#include <cmath>
class Money
{
private:
int dollars;
int cents;
void normalize()
{
if (cents >= 100)
{
dollars += cents / 100;
cents = cents % 100;
}
else if (cents < 0)
{
int dollarsBorrowed = (-cents / 100) + 1;
dollars -= dollarsBorrowed;
cents += dollarsBorrowed * 100;
}
}
public:
// explicit конструктор — захист від випадкових конвертацій
explicit Money(int d = 0, int c = 0) : dollars(d), cents(c)
{
normalize();
}
// Забороняємо конструктор із double — занадто небезпечно для грошей
Money(double) = delete;
int getDollars() const { return dollars; }
int getCents() const { return cents; }
// explicit конвертація у double — лише явний виклик
explicit operator double() const
{
return dollars + cents / 100.0;
}
// Заборона неявної конвертації у int — може призвести до втрати центів
operator int() const = delete;
// Дозволяємо явну перевірку на "порожній гаманець"
explicit operator bool() const
{
return dollars != 0 || cents != 0;
}
// Оператори порівняння
bool operator==(const Money& other) const
{
return dollars == other.dollars && cents == other.cents;
}
bool operator<(const Money& other) const
{
if (dollars != other.dollars)
return dollars < other.dollars;
return cents < other.cents;
}
// Забороняємо порівняння з числами — може призвести до помилок
bool operator==(int) const = delete;
bool operator==(double) const = delete;
friend std::ostream& operator<<(std::ostream& out, const Money& money);
};
std::ostream& operator<<(std::ostream& out, const Money& money)
{
out << "$" << money.dollars << ".";
if (money.cents < 10) out << "0";
out << money.cents;
return out;
}
int main()
{
// ✅ Явне створення
Money wallet(50, 75);
std::cout << "Wallet: " << wallet << "\n";
// ❌ Неявна конвертація заборонена
// Money m1 = 100; // ПОМИЛКА: explicit конструктор
// Money m2 = 19.99; // ПОМИЛКА: конструктор із double видалений
// ✅ Явне створення дозволено
Money m3(100);
Money m4 = Money(25, 50);
std::cout << "m3: " << m3 << ", m4: " << m4 << "\n";
// ✅ Використання у булевому контексті
if (wallet)
{
std::cout << "Wallet is not empty\n";
}
Money empty;
if (!empty)
{
std::cout << "Empty wallet evaluates to false\n";
}
// ✅ Явна конвертація у double
double value = static_cast<double>(wallet);
std::cout << "Wallet value: $" << value << "\n";
// ❌ Заборонені операції
// int i = wallet; // ПОМИЛКА: operator int() видалений
// double d = wallet; // ПОМИЛКА: explicit operator double()
// if (wallet == 50) {} // ПОМИЛКА: operator==(int) видалений
// ✅ Порівняння між Money працює
if (m3 == Money(100))
{
std::cout << "m3 equals $100.00\n";
}
return 0;
}
Рядок 27: explicit Money(int d = 0, int c = 0) — конструктор із explicit забороняє неявні конвертації. Money m = 100; не працює.
Рядок 33: Money(double) = delete; — захист від створення грошей із double (проблема точності: 19.99 може бути 19.989999...).
Рядок 39: explicit operator double() — конвертація у double лише через static_cast.
Рядок 45: operator int() const = delete; — заборона конвертації у int, щоб не втратити центи.
Рядок 48: explicit operator bool() — дозволяє перевірку if (wallet), але забороняє арифметичні операції.
Рядки 68–69: заборона порівняння з числами — захист від помилок на зразок if (wallet == 50) (програміст міг мати на увазі центи або долари?).
Рядки 91–92 (закоментовані): всі небезпечні операції заборонені і викликають чіткі помилки компіляції.
Money демонструє defensive programming — захист від помилок через обмеження можливих операцій. Користувач класу не може випадково створити некоректний стан або виконати небезпечну операцію. Компілятор гарантує коректність на етапі компіляції.Коли використовувати кожен механізм?
explicit конструктор
Використовуйте завжди, якщо конвертація не є очевидною та безпечною.
Приклади:
explicit Dollars(int amount)— захист відDollars d = 100;explicit FileHandle(const char* path)— файл не можна створити випадково
Виключення: типи-обгортки (std::string, std::vector), де неявна конвертація природна.
explicit operator bool()
Використовуйте ЗАВЖДИ для operator bool().
Причина: захист від небезпечних арифметичних операцій (obj + 1) та порівняння з числами (obj == 1).
Дозволяє: використання в умовах if (obj), while (obj), !obj.
= delete для конструкторів
Використовуйте для заборони небезпечних конвертацій.
Приклади:
Money(double) = delete;— заборона грошей ізdouble(проблема точності)MyString(char) = delete;— заборона створення рядка з одного символу
= delete для копіювання
Використовуйте для класів із унікальними ресурсами.
Приклади:
FileHandle(const FileHandle&) = delete;— файл не можна дублюватиMutex(const Mutex&) = delete;— mutex не можна копіювати
Альтернатива: реалізувати глибоке копіювання (стаття 66).
explicit operator Type()
Використовуйте для захисту від неявних конвертацій.
Приклади:
explicit operator double()— конвертація лише черезstatic_castexplicit operator std::string()— явна конвертація у рядок
Без explicit: дозволяйте лише для природних конвертацій (Fraction → double).
= delete для операторів
Використовуйте для заборони безглуздих або небезпечних операцій.
Приклади:
bool operator==(double) = delete;— заборона порівняння зdoubleSafeInt operator/(const SafeInt&) = delete;— заборона ділення
Резюме: безпека через обмеження
У цій статті ми вивчили механізми контролю конвертацій — потужні інструменти для створення безпечних класів:
- Конструктори як оператори конвертації — будь-який конструктор з одним параметром дозволяє неявну конвертацію
explicitконструктор — забороняє неявну конвертацію, дозволяє лише явний виклик- Оператори приведення типів —
operator Type()дозволяє конвертувати об'єкт у інший тип explicit operator Type()— захист від неявної конвертації (особливо дляbool)= delete— явна заборона функцій, конструкторів, операторів
Анонс наступної статті
Ми завершили вивчення перевантаження операторів — від базових арифметичних до складних конвертацій. Але залишилася одна критична тема, без якої наше розуміння класів неповне: управління пам'яттю.
У наступній статті (стаття 66) ми повернемося до проблеми, що виникла у статті 59: поверхневе копіювання (shallow copy) класів із динамічною пам'яттю. Розглянемо:
- Глибоке копіювання (deep copy) — як правильно копіювати об'єкти з динамічними ресурсами
- Перевантаження
operator=— оператор присвоювання із захистом від самоприсвоювання - Правило трьох (Rule of Three) — фундаментальне правило C++: деструктор + конструктор копіювання + оператор присвоювання
- Практична реалізація — клас
IntArrayіз повним управлінням пам'яттю
Це завершальна стаття серії про базові механізми ООП. Після неї ви зможете створювати безпечні, надійні класи, що коректно керують будь-якими ресурсами.
Перевантаження операторів: методи класу та спеціальні оператори
Унарні оператори (-, !, ~), інкремент та декремент (++, --), оператор індексації [], оператор виклику (), оператори присвоювання. Перевантаження через методи класу.
Глибоке копіювання та оператор присвоювання
Shallow vs deep copy, перевантаження operator=, самоприсвоювання, Rule of Three. Правильне управління динамічною памяттю у класах.