3.7 Pointers to members

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

Дословно перечислимый тип

enum E {
    White, Gray, Black
};

int main() {
    int e = White;
    std::cout << e << std::endl; // 0
}

Можно использовать как отдельный тип E, но он хранится в памяти как int и нумеруется начиная с нуля.

В C++-11 появились enum class. Она не вносит интовые константы и запрещает неявные конверсии.

С помощью двоеточия можно описать каким типом он должен представляться(обязательно целочисленным)

enum class E : int8_t {}

IV. Inheritance

4.1 Public, private and protected inheritance


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 имеет доступ к родительским полям.

По сути права доступа к полю “перемножаются” на тип наследования, нужно “пройти” оба модификатора.


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

Что происходит если есть конфликт имён?


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
}

Главный принцип: частное главнее общего.

А что если


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 можно сделать приватным

private:
    using Base::x;

Самый кринж

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.

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 есть дефолтный конструктор, либо нужно писать что-то типа

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

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
}

Суть в том, что наследника можно кастовать к родителям, а вот обратное, ясное дело нельзя, ведь наследник может больше, чем родитель.