Существует заблуждение: наследование - вне концепции чистого ООП, что С ++ не является - это не что иное, как «композиция с неназванным членом с возможностью распада».
Отсутствие виртуальных функций (и деструктор не является особым, в этом смысле) делает ваш объект не полиморфным, но если то, что вы делаете, просто «повторно использует его поведение и выставляет собственный интерфейс» наследование делает именно то, что вы спросил.
Деструкторы не должны быть явно вызваны друг от друга, так как их вызов всегда привязан по спецификации.
#include <iostream>
unsing namespace std;
class A
{
public:
A() { cout << "A::A()" << endl; }
~A() { cout << "A::~A()" << endl; }
void hello() { cout << "A::hello()" << endl; }
};
class B: public A
{
public:
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
int main()
{
B b;
b.hello();
return 0;
}
выведет
A::A()
B::B()
B::hello()
B::~B()
A::~A()
делая вкладывается в B с
class B
{
public:
A a;
B() { cout << "B::B()" << endl; }
~B() { cout << "B::~B()" << endl; }
void hello() { cout << "B::hello()" << endl; }
};
, который будет выводить точно так же.
«Не выводить, если деструктор не является виртуальным» не является обязательным следствием C++, а просто общепринятым не написанным (в спецификации нет ничего: кроме UB-вызова delete на базе) который возникает до C++ 99, когда ООП путем динамического наследования и виртуальных функций является единственной программируемой парадигмой C++.
Конечно, многие программисты во всем мире сделали их кости с такого рода школы (то же самое, что учить iostreams как примитивы, а затем перемещается в массив и указатели, а на самом последнем уроке учитель говорит «о. .. tehre также является STL, который имеет вектор, строку и другие расширенные функции »), и сегодня, даже если C++ стал многопартийным, все еще настаивайте на этом чистом правиле ООП.
В моем примере A :: ~ A() не является виртуальным как A :: hello. Что это значит?
просто: по той же причине, вызывающей A::hello
не приведет вызов B::hello
, вызывающей A::~A()
(на удалении) не приведет к B::~B()
. Если вы можете принять -в вас программирование style- первое утверждение, нет причин, по которым вы не можете принять второй. В моем примере нет A* p = new B
, который получит delete p
, так как A :: ~ A не является виртуальным и Я знаю, что это означает.
Точно та же причина того, что не будет делать, используя второй пример B, A* p = &((new B)->a);
с delete p;
, хотя вторым случаем, совершенно двойственный с первым, выглядит не интересно никому для каких-либо видимых причин.
Единственная проблема заключается в «обслуживании» в том смысле, что - если код yopur просматривается программистом OOP, он откажется от него, а не потому, что он сам по себе не прав, а потому, что ему сказали это сделать.
Фактически, «не получается, если деструктор не является виртуальным», потому что большинство программистов опасаются, что слишком много программистов, которые не знают, что они не могут вызвать delete на указателе на базу. (Извините, если это не вежливо, но после того, как 30 + года опыта программирования я не вижу никакой другой причины!)
Но ваш вопрос иначе:
Вызов B :: ~ B() (по удалению или по завершению области) всегда приведет к A :: ~ A(), поскольку A (независимо от того, встроена ли она или унаследована) в любом случае является частью B.
После Luchian комментарии: Неопределенное поведение намекал выше в своих комментариях связано с удалением на указатель на ан-object's базы, без виртуального деструктора.
Согласно школе ООП, это приводит к правилу «не выводится, если не существует виртуального деструктора».
Что я хочу сказать здесь, заключается в том, что причины этой школы зависят от того, что каждый объект, ориентированный на ООП, должен быть полиморфным и все полиморфным должен быть адресуемым указателем на базу, чтобы разрешить замену объекта , Сделав это утверждение, эта школа намеренно пытается сделать пустоту пересечением между производными и нереагируемыми, чтобы чистая программа ООП не испытала этого UB.
Мое положение просто означает, что C++ - это не только ООП, обязательно обслуживание для замены ООП.
std :: map НЕ является полиморфным, поэтому он НЕ заменяется. MyMap тот же: НЕ полиморфный и НЕ заменяемый.
Он просто должен повторно использовать std :: map и открыть тот же интерфейс std :: map. И наследование - это всего лишь способ избежать длинного шаблона переписанных функций, который просто вызывает повторное использование.
MyMap не будет иметь виртуального dtor, поскольку std :: map не имеет его. И этого - для меня достаточно сказать программисту на C++, что это не полиморфные объекты и которые нельзя использовать друг на друга.
Я должен признать, что эта позиция сегодня не разделяется большинством экспертов C++. Но я думаю (мое единственное личное мнение), это только из-за их истории, которые относятся к ООП как к догме, чтобы служить, а не из-за необходимости С ++. Для меня C++ не является чистым языком ООП и не обязательно должен всегда следовать парадигме ООП в контексте, в котором ООП не соблюдается или не требуется.
+1 Всегда предпочитайте композицию вместо наследования. Еще хотелось бы, чтобы был какой-то способ уменьшить весь код шаблона, необходимый для упаковки. – daramarak
@ daramarak: так ли я, если только что-то вроде 'using attribute.insert;' мог бы работать! С другой стороны, довольно редко вам нужны все методы, и обертка дает возможность дать осмысленное имя и использовать типы более высокого уровня :) –
@daramarak: * По-прежнему хотелось бы, чтобы какой-то способ уменьшить весь шаблонный код необходимо для упаковки *: да, есть: наследование. Но программисты уверены в себе, что не должны использовать его ... потому что они всегда склонны интерпретировать его как «есть». Но это не требование, просто общественное признание. –