| Person |
| name_ | age_ |
| Student |
| name_ | age_ | uni_ |
struct Person {
Person(string name, int age)
: name_(name), age_(age)
{}
};
struct Student : Person {
Student (string name, int age, string uni)
: Person(name, age), uni_(uni)
{}
};
Student
вызывается деструктор Person
. Student s("Alex", 21, "Oxford");
Person & l = s; // Student & => Person &
Person *r = &s; // Student * => Person *
Student s("Alex", 21, "Oxford");
Person p = s; // Person("Alex", 21);
Person (Person const &p)
, который ничего не знает про uni_
.char, signed char, short => int
unsigned char, unsigned short => int/unsigned int
float => double
double
=> int
a
определён конструктор от типа b
struct Person {
string name () const { return name_; }
...
};
struct Professor : Person {
string name () const {
return "Prof. " + Person::name();
}
...
};
Professor pr("Stroustrup");
cout << pr.name() << endl; // Prof. Stroustrup
Person *p = ≺
cout << p.name() << endl; // Stroustrup
struct Person {
virtual string name () const { return name_; }
...
};
struct Professor : Person {
string name () const {
return "Prof. " + Person::name();
}
...
};
Professor pr("Stroustrup");
cout << pr.name() << endl; // Prof. Stroustrup
Person *p = ≺
cout << p.name() << endl; // Prof. Stroustrup
= 0
в конце определения.
struct Person {
virtual string occupation() const = 0;
}
struct Person {
...
};
struct Student : Person {
...
private:
string uni_;
};
int main () {
Person * p = new Student ("Alex", 21, "Oxford");
delete p; // здеcь вызывается деструктор типа Person, и как следствие не очищаются поля дочернего класса => получаем утечку памяти
}
struct Person {
...
virtual ~Person() {}
};
struct Student : Person {
...
private:
string uni_;
};
int main () {
Person * p = new Student ("Alex", 21, "Oxford");
delete p; // вызывается деструктор типа Student
}
| Person |
| vptr | name _ | age_ | uni_ |
| Student |
p->occupation(); // p->vptr[1]();
struct Person {
virtual ~Person() {}
virtual string occupation() = 0;
};
struct Teacher : Person {
string occupation () { ... }
virtual string course () { ... }
};
struct Professor : Teacher {
string occupation () { ... }
virtual string thesis() { ... }
};
Person
index | Method | adress |
---|---|---|
0 | ~Person | 0x1111 |
1 | occupation | 0x0000 |
Teacher
index | Method | adress |
---|---|---|
0 | ~Teacher | 0x2222 |
1 | occupation | 0x3333 |
2 | course | 0x4444 |
Professor
index | Method | adress |
---|---|---|
0 | ~Professor | 0x5555 |
1 | occupation | 0x6666 |
2 | course | 0x4444 |
3 | thesis | 0x7777 |
В при вызове виртуального метода в конструкторе\деструкторе происходит вызов метода текущего класса, даже если в одном из дочерних классов этот метод переопределён.
struct Person {
Person(string const name) : name_(name) {}
virtual ~Person() {}
virtual string name() const {
return name_;
}
private:
string name_;
};
struct Teacher : Person {
Teacher(string const name) : Person(name) {
cout << name();
}
};
struct Professor : Person {
string name () {
return "Prof. " + name_;
}
};
Professor p("Stroustrup"); // "Stroustrup"
Такое поведение позоляет предотвратить доступ к неинициализированным полям. Т.к. конструкторы вызываются от базового к дочернему (Person => Teacher => Professor), то из конструктора Teacher мы не должны иметь доступ к полям Professor
Это реализовано следующим образом:
struct NetworkDevice {
void send(void * data, size_t size) {
log("start sending");
send_impl(data, size);
log("end");
};
private:
virtual void send_impl(void * data, size_t size) {}
};
struct Router : NetworkDevice {
private
void send_impl(void * data, size_t size) {}
}
NetworkDevice
при посылке информации по сети всегда будет записывать информацию в логи. struct NetworkDevice {
virtual void send(void * data, size_t t) = 0;
};
void NetworkDevice::send(void * data, size_t size) { ... }
struct Router : NetworkDevice {
void send (void * data, size_t size) {
// не виртуальный метод
// вызываем как реализацию по умолчанию
NetworkDevice::send(data, size);
}
};
struct IClonable {
virtual ~IClonable() {}
virtual IClonable * clone() const = 0;
};
Shape
, Rectanble
, Square
. Кажется логичным унаследовать таким образом Shape => Rectangle => Square
. Но если Square
наследуется от Rectangle
то мы нарушаем принцип подстановки. К примеру у Rectangle
есть метод setWidth
. Аналогичный метод для square
должен будет установить и width
и height
=> поведение класса наследника отличается от поведения базового класса => нарушается принцип подставновки.public
, protected
, private
.
public
наследование - информацию о том что класс B
является наследником класса A
(ссылку\указатель на B
можно приветсти к ссылке\указателю на A
, по ссылке класса B
можно вызывать методы класса A
) известна внутри класса B
, внутри всех наследников класса B
и во внешнем коде.
class B : public A {};
protected
- информацию о том что класс B
является наследником класса A
известна внутри класса B
и внутри всех наследников класса B
private
- информацию о том что класс B
является наследником класса A
известна внутри класса B
.public
, классы с модификатором доступа private
.private
и protected
наследование можно заменить агрегированием.private
и protected
наследований целесообразно, если необходимо не только агрегировать другой класс, но и переопределить его виртуальные методы.C++
разрешено множественное наследование
struct Person {};
struct Student : Person {};
struct Worker : Person {};
struct WorkingStudent : Student, Worker {};
WorkingStudent
будет содержаться две копии объекта Person
. Причём эти данные не будут синхронизироваться. Более того при попытке вызывать функцию класса Person
получим неоднозначное поведение, т.к. данная фунция определена дважды. struct IWorker {};
struct Worker : Person, IWorker {};
struct Student : Person {};
struct WorkingStudent : Student, IWorker {};