Added 08.md

This commit is contained in:
thematdev 2023-10-23 20:06:43 +03:00
parent 5e199a9c83
commit 112832ea4b
Signed by: thematdev
GPG Key ID: D12878639B090D90

324
pages/mipt_cxx1/08.md Normal file
View File

@ -0,0 +1,324 @@
## 3.5 Const, static and explicit methods
```C++
struct S {
void f() {
std::cout << "Hi!";
}
};
int main() {
const S s;
s.f(); // CE
}
```
По умолчанию методы у классов считаются неконстантными. То есть `S::f()` не определена для `const S`,
потому что она неконстанта. Константность нужно явно уточнить.
```C++
void f() const { // Now OK
std::cout << "Hi!";
}
```
Можно делать перегрузку по признаку константности, ровно как можно делать перегрузку по константности аргумента,
ведь объект класса это неявный "нулевой" аргумент.
```C++
struct S {
void f() const {
std::cout << 1;
}
void f() {
std::cout << 2;
}
};
int main() {
S s;
const S& r = s;
r.f(); // 1
}
```
**Пример**. Квадратные скобки для строки
```C++
struct S {
char arr[10];
char& operator[](size_t index) { // for non-const access
return arr[index];
}
const char& operator[](size_t index) const { // for const access
return arr[index];
}
};
```
Почему нельзя заменить `const char&` на `char`?
```C++
int main() {
String s = "abcd";
const String &cs = s;
const char &c = cs[0];
s[0] = 'b';
// if operator[] returns const char&, c will be 'b', otherwise 'a'
}
```
Почему `char& operator[](size_t index) const` вообще скомпилируется?
`char *arr` превращается в `char* const arr`, а не в `const char* arr`,
поэтому при обращении к `arr` по индексу мы получаем неконстантную ссылку,
поэтому всё компилируется.
К сожалению, следующий код компилируется
```C++
int x = 0;
struct S {
int& r = x;
void f(int y) const {
r = y;
}
};
```
Поскольку ссылка это по факту указатель, `const` на него не влияет.
А что если мы хотим менять поле у константного объекта? Для этого есть ключевое слово `mutable`.
```C++
struct S {
mutable int n_calls = 0;
void f() {
++n_calls;
std::cout << "Hello!" << std::endl;
}
};
```
Это даже может быть полезно, например, при реализации сплей-дерева в виде класса. Сплей-дерево
после вызова `find` вызывает `splay` и перестраивает дерево. Но как быть, ведь `find` по-хорошему должен быть
константным. Здесь как раз помогает ключевое слово `mutable`.
`static` методы -- те, которые "относятся к классу в целом". Для полей оно значит то же, что и для глобальных
переменных.
```C++
struct S {
static int x; // will be in static memory
// Can be accessed via S::f();
static void f() {
std::cout << "Hi!" << std::endl;
}
};
```
Классический пример -- синглтон, класс, который должен существовать ровно один в программе,
например, соединение с базой данных.
```C++
class Singleton {
private:
Singleton() { /* i.e open connection */ }
static Singleton* ptr = nullptr;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getObject() {
if (ptr == nullptr) {
ptr = new Singleton();
}
return *ptr;
}
};
```
Ключевое слово `explicit` запрещает вызывать методы неявно.
```C++
struct Latitude {
double value;
// Latitude(double value_) : value(value_) {}
// f(Latitude) can be called by f(0.5) via implicit conversion
// and we can confuse it with longitude
explicit Latitude(double value_) : value(value_) {} // now implicit conversion is forbidden
};
```
А что если мы хотим приведение от `Latitude` к `double`?
```C++
operator double() const { // f(double) can be called via f(Latitude)
return value;
}
// starting from C++-11 cast operators can be made explicit
```
В случае если оператор приведения был объявлен `explicit` можно воспользоваться `static_cast`.
Конструкторы из одного аргумента или операторы приведения типов лучше делать `explicit`, но не всегда.
**Важный пример.** Даже оператор конверсии `BigInteger` в `bool` нужно будет сделать `explicit`. Для if-ов
добавили специальный костыль под названием contextual conversion, она происходит под `if, while` и тернарным оператором.
Это особый вид конверсии, который разрешает рассматривать `explicit` конверсии.
Начиная с C++-11 можно определять свои литеральные суффиксы.
```C++
class BigInteger {};
// either unsigned long long, long double, const char*
BigInteger operator""_bi(unsigned long long x) {
return BigInteger(x);
};
1329_bi; // valid BigInteger
```
Стандартная строка также обладает литеральным суффиксом, `"abcdef"s` будет `std::string`, а не `const char*`.
## 3.6 Operators overloading
```C++
struct Complex {
double re = 0.0;
double im = 0.0;
Complex(double re_) : re(re_) {}
Complex(double re_, double im_) : re(re_), im(im_) {}
Complex operator+(const Complex &other) const {
return Complex{re + other.re, im + other.im};
}
};
int main() {
Complex c(1.0);
c + 3.14; // OK, c.operator+(3.14)
3.14 + c; // CE, ???
}
```
Если нужно определить бинарный арифметический оператор, то лучше объявить его вне класса.
```C++
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.re + b.re, a.im + b.im);
}
```
`operator+=` уже нужно определять внутри класса
```C++
Complex& operator+=(const Complex &other) {
*this = *this + other;
return *this;
}
```
Это **очень** плохой код, в том смысле, что он неэффективный. Например, для строки это в два раза хуже,
чем обычная реализация. Лучше реализовать `+` через `+=`.
```C++
Complex operator+(const Complex &a, const Complex &b) {
Complex result = a;
result += b;
return result;
}
```
А не надо ли поставить `const`?
```C++
int main() {
Complex a(1.0), b(2.0), c(3.0);
a + b = c; // Why it compiles?
}
```
Но слева же `rvalue`. А с чего мы взяли, что нельзя присваивать что-то `rvalue` для нестандартных типов?
Один из способов решения это поставить `const` перед возвращаемым значением.
**Но это было актуально до C++-11, сейчас так лучше не делать**
```C++
struct Complex {
Complex& operator=(const Complex& other) & {
} // now it can be applied only to lvalue
Complex &operator=(const Complex &other) && {
} // only to rvalue
};
```
**Внимание**. В коде ниже происходит лишнее копирование
```C++
Complex operator+(Complex a, const Complex &b) {
return a += b;
}
```
Так как компилятор в первом случае применит return value optimization, а здесь нет.
Можно также перегружать оператор вывода
```C++
std::ostream& operator<<(std::ostream &out, const String &str) {
}
```
Аналогично оператор ввода из потока
```C++
std::istream& operator>>(std::istream &in, String &str) {
}
```
Оператор ввода иногда разумно сделать `friend`.
Оператор возвращает тот же поток чтобы можно было использовать синтаксис `std::cout << x << y`.
Нельзя определить символ для обозначения оператора, приоритет оператора, порядок вычисления.
```C++
bool operator<(const Complex &a, const Complex &b) {
return a.re < b.re || (a.re == b.re && a.im < b.im);
}
bool operator>(const Complex &a, const Complex &b) {
return b < a; // same time as operator<
}
bool operator<=(const Complex &a, const Complex &b) {
return !(a > b);
}
```
То есть желательно всё, кроме равенства выразить через `operator<`.
Начиная с C++-20 есть оператор "космический корабль", также известный как three-way comparison.
```C++
??? operator<=>(const Complex &other) = default; // automatic deduction lexicographically
```
Возвращает один из трех типов `std::weak_ordering, std::strong_ordering, std::partial_ordering`.
`std::partial_ordering: less, greater, equivalent, unordered`.
Разница между `std::strong_ordering, std::weak_ordering` в том, когда достигается равенство.
`std::strong_ordering` значит, что `a == b => forall f: f(a) == f(b)`.
В строке, например, стандартный оператор `<=>` так как надо сравнивать не указатели, а значения под ними.
Все эти вещи определены в заголовочном файле `<compare>`.