325 lines
10 KiB
Markdown
325 lines
10 KiB
Markdown
## 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>`.
|