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.*p; // returns s.x;
s
* ps = &s;
S->*p; // returns s.x;
ps
void (S::* pf)(int) = &S::f; // pointer to method
(s.*pf)(3);
(ps->*pf)(5)
}
Интуитивно указатель на поле хранит “сдвиг относительно начала структуры”.
Дословно перечислимый тип
enum E {
, Gray, Black
White};
int main() {
int e = White;
std::cout << e << std::endl; // 0
}
Можно использовать как отдельный тип E
, но он хранится в
памяти как int
и нумеруется начиная с нуля.
В C++-11 появились enum class
. Она не вносит интовые
константы и запрещает неявные конверсии.
С помощью двоеточия можно описать каким типом он должен представляться(обязательно целочисленным)
enum class E : int8_t {}
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
.y; // OK, default inheritance for struct is public
s}
Как работает дружба при наследовании?
Допустим в struct Granny
мы объявили main
своим другом. Тогда теперь мы всё ещё не сможем обратиться к
s.x
. И это логично, ведь friend
снимает все
ограничения, которые ты наложил, но не те, которые наложил кто-то
другой, например, твой наследник.
Строгая мама запрещает общаться с доброй бабушкой
Что происходит если есть конфликт имён?
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.f() // OK, 2
d}
Главный принцип: частное главнее общего.
А что если
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.f(0) // ???
d}
Программа выведет 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
, дабы подчеркнуть, что имя берется
из глобальной области.
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 {
(int) { std::cout << "A " << x << std::endl; }
A};
struct Base {
;
A x(int x): x(x) { std::cout << "Base" << std::endl; }
Base};
struct Derived : Base {
;
A y(double y): Base(0), y(y) { std::cout << "Derived" << std::endl; }
Derived}
int main() {
= 1;
Derived d }
Программа выведет
A 0
Base
A 1
Derived
Если написать деструкторы, то так как сначала выполняется тело деструктора, а потом уничтожаются поля, причём в обратном порядке, то выведется
~Derived
~A 1
~Base
~A 0
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(d); // OK, can cast Derived to Base
f}
Суть в том, что наследника можно кастовать к родителям, а вот обратное, ясное дело нельзя, ведь наследник может больше, чем родитель.