- Def Наследование — концепция объектно-ориентированного программирования, согласно которой абстрактный тип данных может наследовать данные и функциональность некоторого существующего типа, способствуя повторному использованию компонентов программного обеспечения
struct Base {
int x = 1;
};
struct Derived : Base { // default = public
int x = 2;
};
int main() {
Derived d;
std::cout << d.x; // 2
std::cout << d.Base::x; // 1
std::cout << sizeof(d); // 8 (int + int)
}#include <iostream>
struct A {
void foo() { std::cout << "A::foo()\n"; }
void foo(double) { std::cout << "A::foo(double)\n"; }
};
struct B : public A {
void foo(int) { std::cout << "B::foo(int)\n"; }
};
struct C : public A {
using A::foo;
void foo(int) { std::cout << "C::foo(int)\n"; }
};
struct D : public A {
using A::foo;
void foo() { std::cout << "D::foo()\n"; }
};
struct E : public A {
private:
void foo(int) { std::cout << "E::foo(int)\n"; }
};
int main() {
B b;
// b.foo(); // No name: затмили foo()
b.A::foo();
b.foo(10);
C c;
c.foo();
c.foo(10);
c.foo(3.2);
D d;
d.foo();
E e;
// e.foo(100); // -> foo(int) - private
}#include <iostream>
struct A {
void foo() { std::cout << "A\n"; }
};
struct B : public A {
void foo() { std::cout << "B\n"; }
void bar() {}
};
struct C : private A {};
int main() {
A a;
B b;
C c;
a = b; // Можно, срезка при копировании
// b = a; Нет
// c = a; Нет
// a = c; Нет
A* aptr1 = &b;
aptr1->foo();
b.foo();
// A* aptr2 = &c;
A& aref = b;
aref.foo();
B* bptr = &b;
A* ap = bptr;
// bptr = ap;
bptr = static_cast<B*>(ap); // работает, но аккуратно
}- Модификатор наследования задается следующим синтаксисом:
struct Base {};
struct Derived : public Base {};-
Второе отличие классов от структур: в классах модификатор наследования по умолчанию private, в структурах - public.
-
Модификатор наследования влияет на то, с каким модификатором доступа будут поля и методы родителя в наследнике:
| public inheritance | protected inheritance | private inheritance | |
|---|---|---|---|
| public base members | public | protected | private |
| protected base members | protected | protected | private |
| private base members | not accessible | not accessible | not accessible |
| Inheritance | Approximate meaning |
|---|---|
B : public A |
B является A |
B : protected A |
B является A в узких кругах |
B : private A |
B реализуется через A |
Check the example:
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};- Everything that is aware of
Baseis also aware thatBasecontainspublicMember. - Only the children (and their children) are aware that
BasecontainsprotectedMember. - No one but
Baseis aware ofprivateMember.
"Is aware of" means "acknowledge the existence of, and thus be able to access".
The same happens with public, private and protected inheritance. Let's consider a class Base and a class Child that inherits from Base.
- If the inheritance is
public, everything that is aware ofBaseandChildis also aware thatChildinherits fromBase. - If the inheritance is
protected, onlyChild, and its children, are aware that they inherit fromBase. - If the inheritance is
private, no one other thanChildis aware of the inheritance.
Иначе говоря, про приватное наследование знает только наследник
На русском:
- Public наследование - факт наследования известен всем
- Protected наследование - факт наследования известен наследнику и его наследникам
- Private наследование - факт наследования известен только наследнику
A<- public <-B<- public <-C
| A members | B accessible | C accessible |
|---|---|---|
public x |
public x |
public x |
protected y |
private y |
N/A |
| N/A | N/A | N/A |
#include <iostream>
struct A {
public:
int x = 0;
protected:
int y = 10;
private:
int z = 20;
};
struct B : public A {
void foo() {
std::cout << x << '\n'; // OK
std::cout << y << '\n'; // OK
// std::cout << z << '\n'; // CE
}
};
struct C : private A {
void foo() {
std::cout << x << '\n'; // OK
std::cout << y << '\n'; // OK
// std::cout << z << '\n'; // CE
}
};
struct D : protected A {
void foo() {
std::cout << x << '\n'; // OK
std::cout << y << '\n'; // OK
// std::cout << z << '\n'; // CE
}
};
struct B1 : public B {};
int main() {
B b;
std::cout << b.x << '\n'; // OK
// std::cout << b.y << '\n'; // CE
// std::cout << b.z << '\n'; // CE
C c;
// std::cout << c.x << '\n'; // CE
// std::cout << c.y << '\n'; // CE
// std::cout << c.z << '\n'; // CE
D d;
// std::cout << d.x << '\n'; // CE
// std::cout << d.y << '\n'; // CE
// std::cout << d.z << '\n'; // CE
}#include <iostream>
struct Base {
protected:
void foo() { std::cout << 1; }
};
struct Derived : Base {
using Base::foo;
void foo(int) { std::cout << 2; }
};
int main() {
Derived d;
d.foo();
return 0;
}class Base {
int a;
char b;
};
class Derived: Base {
double c;
};sizeof(Derived)= 16:- 4 -
int - 1 -
char - 3 -
alignment - 8 -
double
- 4 -
- Если бы в объекте
Baseне было полей, тоDerivedсостоял бы просто из одногоdouble(empty base optimization)
- Def Машинном словом называется единица данных, которая выбрана естественной для данной архитектуры
- Процессор считывает из оперативной памяти данные и кладет их в свою память: регистры. Размер регистра это и есть машинное слово
- Соответственно за раз из памяти читается машинное слово: в случае
x86-64это 8 байт.
[байт 0] [байт 1] [байт 2] [байт 3] [байт 4] [байт 5] [байт 6] [байт 7]
[ нужно это машинное слово ]
[ но приходится читать это ] [ и это ]
- Чтобы таких ситуаций не происходило C++ "выравнивает" данные за счет добавления "мусорных" байтов. Выравнивание происходит по 4 байтам
struct S {
char x; // 1 байт
int y; // 4 байта
char z; // 1 байт
};[ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]
[x ] { потерянные } [ y ] [z ] { потерянные }
#include <iostream>
struct A {
A() {std::cout << "A\n"; }
~A() {std::cout << "~A\n"; }
};
struct B {
B() {std::cout << "B\n"; }
~B() {std::cout << "~B\n"; }
};
struct Base {
Base() {std::cout << "Base\n"; }
~Base() {std::cout << "~Base\n"; }
A a;
};
struct Derived: Base {
Derived() {std::cout << "Derived\n"; }
~Derived() {std::cout << "~Derived\n"; }
B b;
};
int main() {
Derived d;
// A Base B Derived ~Derived ~B ~Base ~A
}struct Base {
Base(int a) {}
};
struct Derived: Base {
Derived(int a, double d): Base(a), d_(d) {}
double d_;
};- ТОЛЬКО публичное наследование позволяет кастовать ребенка к родителю
- Даже если переопределить C-style
operator Baseв случаеprotected/privateнаследования - Однако в случае
protected/privateнаследования кастовать МОЖНО, но в детях/ребенке соответственно.
- Даже если переопределить C-style
struct Base {
int x;
};
struct Derived : Base {
int y;
};
int main() {
Derived d;
Base b = d; // Base, который лежит в Derived (срезка при копировании)
Base& bb = d; // ссылка на Base, где на самом деле лежит Derived
}struct Base {};
struct Derived : private Base {
void foo(Derived& d) { Base& b = d; }
};
struct Derived2 : public Derived {
void bar(Derived& d) { Base& b = d; } // CE
};
int main() {
Derived d;
Base& b = d; // CE
}memory of derived: x,y (Base,y)
Base* b = &d
- Происходит следующая проблема: что если Base поддерживает метод, который нелогично применять к Derived.
- Пример: многоугольник поддерживает метод, который сдвигает одну из точек в произвольное место. Прямоугольник такой метод поддерживать не может.
Def Принцип подстановки Барбары Лисков: пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.
Note Принцип подстановки Барбары Лисков входит в принципы SOLID:
- SRP (single responsibility principle) - для каждого класса должно быть определено единственное назначение. Все ресурсы, необходимые для его осуществления, должны быть инкапсулированы в этот класс и подчинены только этой задаче.
- OCP (open-closed principle) - программные сущности должны быть открыты для расширения, но закрыты для модификации
- LSP (Liskov substitution principle)
- ISP (interface segregation principle) - много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения
- DIP (dependency inversion principle) - зависимость на Абстракциях. Нет зависимости на что-то конкретное
class Base {};
class Derived : private Base {}; // с protected аналогично
int main() {
Base& b = d; // CE
Base bb = d; // CE
}Base b;
Derived& d = static_cast<Derived&>(b); // UB - захватываем лишнюю память
Derived* d_ptr = static_cast<Derived*>(&b); // UB - захватываем лишнюю памятьstruct C: A, B { ... };
- Размещение в пямяти работает по следующему принципу: сначала родители в том порядке, в котором они унаследованы, потом наследники. С конструкторами и деструкторами то же самое.
dи&sмогут численно не совпадать (если классы-родители не пустые) (потому чтоDadлежит вSonправее начала наsizeof(Mom))
#include <iostream>
struct Mom { int a = 1; };
struct Dad { int b = 2; };
struct Son: Mom, Dad { int c = 3; };
int main() {
Son s;
std::cout << *(reinterpret_cast<int*>(&s));
std::cout << *(reinterpret_cast<int*>(&s) + 1);
std::cout << *(reinterpret_cast<int*>(&s) + 2) << std::endl;
Mom* m = &s;
std::cout << *(reinterpret_cast<int*>(m));
std::cout << *(reinterpret_cast<int*>(m) + 1) << std::endl;
Dad* d = &s;
std::cout << *(reinterpret_cast<int*>(d));
std::cout << *(reinterpret_cast<int*>(d) + 1) << std::endl;
// Output: 123, 12, 23
}struct Granny { int x; };
struct Mother: Granny { int y; };
struct Dad: Granny { int z; };
struct Son: Mother, Dad { int t;};
int main() {
Son s;
s.x; // CE
Granny& g = s; // CE
}- Еще одна проблема:
struct Granny {};
struct Mom: Granny {};
struct Son: Mom, Granny {};- В данном случае ко второй бабушке можно обратиться только через трюки с
reinterpret_castи сдвигами указателей - Note Такая ситуация называется inaccessible base class
- See [[DynamicPolymorphism]]
struct S {
public:
int x;
protected:
int y;
private:
int z;
};
struct S1 : private S {
friend class C;
};
class C {
public:
void foo() {
S s;
S1 s1;
s.x;
s1.x;
s1.y;
// s1.z; - private in S is not accessible for S1
// s.y; - relationship friend-parent is not transitive
}
};
int main() {
C c;
c.foo();
}#include <iostream>
struct S {
public:
int x;
protected:
int y;
private:
int z;
};
struct S1 : private S {};
struct S2 : public S1 {
void foo(::S s) { // pure S s is CE
s.x;
// s.y; CE
}
};
int main() {
S2 s;
s.foo(S{});
}#include <iostream>
struct A;
struct B;
struct A {
protected:
int x;
};
void foo(const B&, const A&);
struct B : public A {
friend void foo(const B& b, const A& a) {
b.x;
// a.x; CE
}
};
int main() {
}For instance, if you have a dependent class which derives from a dependent base, but within an scope in which a name from the base class apparently doesn't depend on the template, you might get a compiler error just like below.
#include <iostream>
template <class T>
class Dependent {
protected:
T data;
};
template <class T>
class OtherDependent : public Dependent<T> {
public:
void printT() const {
std::cout << "T: " << data << std::endl; // ERROR
}
};
int main() {
OtherDependent<int> o;
o.printT();
return 0;
}This error happens because the compiler will not lookup name data inside the base class template since it doesn't dependent on T and, consequently, it is not a depedent name. The ways to fix are using this or explicitly telling the dependent base class template:
std::cout << "T: " << this->data << std::endl; // Ok now.
std::cout << "T: " << Dependent<T>::data << std::endl; // Ok now.or placing using declaration:
template <class T>
class OtherDependent : public Dependent<T> {
using Dependent<T>::data; // Ok now.
...
};#include <iostream>
template <typename T>
struct Base {
void foo() {
std::cout << "Foo\n";
}
};
template <typename T>
struct Derived : Base<T> {
void bar() {
// foo() // foo - independent name
Base<T>::foo(); // foo - dependent name
this->foo(); // this - dependent name
}
};
int main() {
Derived<int> d;
d.bar();
}#include <iostream>
void foo() { std::cout << "Global\n"; }
template <typename T>
struct Base {
void foo() { std::cout << "Base\n"; }
};
// void foo() { std::cout << "Global\n"; } // is the same that foo firstly
template <typename T>
struct Derived : Base<T> {
void bar() {
foo(); // foo -independent name
Base<T>::foo(); // foo - dependent name
this->foo(); // this - dependent name
}
};
int main() {
Derived<int> d;
d.bar();
}