pandoc-pages/pages/mipt_cxx1/08.md
2023-10-23 20:06:43 +03:00

325 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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>`.