Added 08.md
This commit is contained in:
parent
5e199a9c83
commit
112832ea4b
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>`.
|
Loading…
Reference in New Issue
Block a user