diff --git a/pages/mipt_cxx1/08.md b/pages/mipt_cxx1/08.md new file mode 100644 index 0000000..d095942 --- /dev/null +++ b/pages/mipt_cxx1/08.md @@ -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)`. + +В строке, например, стандартный оператор `<=>` так как надо сравнивать не указатели, а значения под ними. + +Все эти вещи определены в заголовочном файле ``.