pandoc-pages/pages/mipt_cxx1/09.md
2023-11-05 19:32:12 +03:00

308 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 3.7 Pointers to members
```C++
struct S {
int x;
double y;
void f(int z) {
std::cout << x + z << std::endl;
}
};
int main() {
int S::* p = &S::x;
S s;
s.*p; // returns s.x;
S* ps = &s;
ps->*p; // returns s.x;
void (S::* pf)(int) = &S::f; // pointer to method
(s.*pf)(3);
(ps->*pf)(5)
}
```
Интуитивно указатель на поле хранит "сдвиг относительно начала структуры".
## 3.8 Enums and enum classes
Дословно перечислимый тип
```C++
enum E {
White, Gray, Black
};
int main() {
int e = White;
std::cout << e << std::endl; // 0
}
```
Можно использовать как отдельный тип `E`, но он хранится в памяти как `int` и нумеруется начиная с нуля.
В C++-11 появились `enum class`. Она не вносит интовые константы и запрещает неявные конверсии.
С помощью двоеточия можно описать каким типом он должен представляться(обязательно целочисленным)
```C++
enum class E : int8_t {}
```
# IV. Inheritance
## 4.1 Public, private and protected inheritance
```C++
class Base {
protected:
int x;
public:
void f() {}
};
class Derived : Base {
int y;
void g() {
std::cout << x << std::endl; // x is inherited from Base, and protected
}
};
int main() {
Derived d;
// std::cout << d.x << std::endl; CE, x is protected
}
```
Ключевое слово `protected` означает то, что в отличие от `private` данный член класса будет доступен еще и его наследникам.
Наследование также бывает публичным, приватным, и защищенным.
По умолчанию у структур оно публичное, у классов приватное.
Публичное наследование значит, что "все знают" о том, что `Derived` является наследником `Base`, а приватное значит, что "никто не
знает".
Например, если мы сделаем `Derived : private Base`, то из `main` мы не сможем вызвать `d.f()`, так как хоть `f` и публично в `Base`, мы
унаследовали его приватно.
Если мы сделаем `Derived : public Base`, то мы все равно не сможем обратится к `d.x`, так как `d.x` приватно в `Base`.
Защищенное наследование значит, что только лишь друзья, наследники, и сам `Derived` имеет доступ к родительским полям.
По сути права доступа к полю "перемножаются" на тип наследования, нужно "пройти" оба модификатора.
```C++
struct Granny {
int x;
void f() {}
};
struct Mom : protected Granny {
int y;
void g() {
std::cout << x << std::endl;
}
};
struct Son : Mom {
int z;
void h() {
std::cout << x << std::endl // OK, can pass protected modifier
}
};
int main() {
Son s;
// s.x; cannot pass protected modifier from Mom to Granny
s.y; // OK, default inheritance for struct is public
}
```
Как работает дружба при наследовании?
Допустим в `struct Granny` мы объявили `main` своим другом.
Тогда теперь мы всё ещё не сможем обратиться к `s.x`. И это логично, ведь `friend` снимает все ограничения, которые ты наложил, но не те,
которые наложил кто-то другой, например, твой наследник.
> Строгая мама запрещает общаться с доброй бабушкой
## 4.2 Visibility
Что происходит если есть конфликт имён?
```C++
struct Base {
int x;
void f() {
std::cout << 1 << std::endl;
}
};
struct Derived : Base {
int y;
void f() {
std::cout << 2 << std::endl;
}
};
int main() {
Derived d;
d.f() // OK, 2
}
```
Главный принцип: астное главнее общего_.
А что если
```C++
struct Base {
int x;
void f(int) {
std::cout << 1 << std::endl;
}
};
struct Derived : Base {
int y;
void f(double) {
std::cout << 2 << std::endl;
}
};
int main() {
Derived d;
d.f(0) // ???
}
```
Программа выведет 2. Более того, если бы `f` не принимала аргументов, то была бы ошибка компиляции. Полезно думать об этом так,
`Derived::f` затмевает `Base::f` как будто бы это более локальная область видимости.
Если мы хотим явно вызваться от родителя, то можно написать `d.Base::f(0);`.
Приватность и публичность также не влияет на то, какой метод мы будем вызывать, свой или родительский.
Чтобы научиться выбирать, нужно написать `using Base::f` внутри `Derived`. Более того, `using Base::f` игнорирует родительский
модификатор доступа, что логично.
Сначала создается область видимости, потом проверяются права доступа.
Сам `using` можно сделать приватным
```C++
private:
using Base::x;
```
Самый кринж
```C++
struct Granny {
int x;
void f() {}
};
struct Mom: private Granny {
friend int main();
int x;
};
struct Son : Mom {
int x;
void f(Granny& g) {
std::cout << g.x << std::endl;
}
};
```
Оно не скомпилируется, так как `Granny` из области видимости сына приватное и он не имеет к нему доступа. Поэтому нужно писать `::Granny &g`, дабы подчеркнуть, что имя берется из глобальной области.
## 4.3 Memory layout, constructors and destructors in case of inheritance.
```C++
struct Base {
int x;
};
struct Derived : Base {
double y;
};
int main() {
sizeof(Derived); // OK, 16. First x, then y, according to padding it's 2 * 8 = 16
}
```
А что если `Base` вообще не содержит полей, только, возможно, методы. `sizeof(Base) > 0`, так как структура должна иметь хоть какой-то
размер, чтобы разные размеры имели разные адреса.
Но тем не менее, `sizeof(Derived) == 8`. Данный феномен именуется EBO (Empty Base Optimization). Пустому `Base` разрешается ничего не
занимать в памяти.
При конструкции всегда должен **сначала** инициализироваться родитель. То есть либо у `Base` есть дефолтный конструктор, либо нужно
писать что-то типа
```C++
struct A {
A(int) { std::cout << "A " << x << std::endl; }
};
struct Base {
A x;
Base(int x): x(x) { std::cout << "Base" << std::endl; }
};
struct Derived : Base {
A y;
Derived(double y): Base(0), y(y) { std::cout << "Derived" << std::endl; }
}
int main() {
Derived d = 1;
}
```
Программа выведет
```
A 0
Base
A 1
Derived
```
Если написать деструкторы, то так как сначала выполняется тело деструктора, а потом уничтожаются поля, причём в обратном порядке, то выведется
```
~Derived
~A 1
~Base
~A 0
```
## 4.4 Casts in case of inheritance
```C++
struct Base {
int x = 1;
};
struct Derived : Base {
int y = 2;
};
void f(Base& b) {
std::cout << b.x << std::endl;
}
int main() {
Derived d;
f(d); // OK, can cast Derived to Base
}
```
Суть в том, что наследника можно кастовать к родителям, а вот обратное, ясное дело нельзя, ведь наследник может больше, чем родитель.