| 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 => intunsigned char, unsigned short => int/unsigned intfloat => doubledouble => inta определён конструктор от типа 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 и внутри всех наследников класса Bprivate - информацию о том что класс 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 {};