Added 08.md
This commit is contained in:
		
							
								
								
									
										324
									
								
								pages/mipt_cxx1/08.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								pages/mipt_cxx1/08.md
									
									
									
									
									
										Normal 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>`.
 | 
			
		||||
		Reference in New Issue
	
	Block a user