## 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)`. В строке, например, стандартный оператор `<=>` так как надо сравнивать не указатели, а значения под ними. Все эти вещи определены в заголовочном файле ``.