diff --git a/pages/mipt_cxx1/09.md b/pages/mipt_cxx1/09.md new file mode 100644 index 0000000..78309cd --- /dev/null +++ b/pages/mipt_cxx1/09.md @@ -0,0 +1,307 @@ +## 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 +} +``` + +Суть в том, что наследника можно кастовать к родителям, а вот обратное, ясное дело нельзя, ведь наследник может больше, чем родитель.