gcc4.9.Реализация 2 в libstdc++ std:: vector наследуется от базы векторов (не виртуального дестуктора). Почему это нормально? [дубликат]
этот вопрос уже есть ответ здесь:
- ты не должен наследовать от std:: vector 12 ответов
поэтому я некоторое время использовал контейнер, полученный из std::vector. Возможно, это плохое дизайнерское решение по нескольким причинам, и вопрос о том, следует ли вам это делать, был подробно обсуждается здесь:
ты не должен наследовать от std:: vector
подкласс/наследовать стандартные контейнеры?
существует ли какой-либо реальный риск для получения из контейнеров STL C++?
можно ли наследовать реализацию из контейнеров STL, а не делегировать?
Я уверен, что пропустил некоторые обсуждения...но разумные аргументы для обеих точек зрения находятся в ссылках. Насколько я могу судить, "потому что ~vector() не является виртуальным" является основой для "правила", которое вы не должны наследовать от контейнеров stl. Однако, если я посмотрю на реализацию для std:: vector в g++ 4.9.2, я найду, что std:: vector наследует от _Vector_base, а _Vector_base-не виртуальный деструктор.
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{
...
~vector() _GLIBCXX_NOEXCEPT
{ std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
_M_get_Tp_allocator()); }
...
}
где:
template<typename _Tp, typename _Alloc>
struct _Vector_base
{
...
~_Vector_base() _GLIBCXX_NOEXCEPT
{ _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage
- this->_M_impl._M_start); }
...
}
таким образом, реализация gcc 4.9.2 std::vector наследуется от базовых классов с не виртуальным деструктором. Это приводит меня к убеждению, что это приемлемая практика. Почему это нормально? Каковы конкретные условия, при которых такая практика не опасно?
6 ответов
1: std::vector
не наследуется от _Vector_base
или, скорее, не в моей стандартной реализации библиотеки. libc++ реализует вектор так:
namespace std
{
template <class T, class Allocator = allocator<T> >
class vector
{
...
конечно, ваша реализация может наследовать от базового класса, но моя реализация не. Это приводит к первой ошибке вашей интерпретации "правила":
стандартная библиотека C++ может быть реализована различными способами, и мы действительно не можем делать широкие, широкие предположения или заявления о стандартный библиотека (та, которая определена ISO/IEC 14882:2014
) от его реализации.
Итак, прежде чем мы продолжим, давайте вспомним: мы не собираемся фокусироваться на одной реализации в этом ответе. Мы собираемся рассмотреть все реализации (сосредоточившись на следующих определениях в стандарте C++; не определения конкретного файла заголовка на жестком диске).
2: да, так std::vector
наследует от _Vector_base
но прежде чем мы продолжим, давайте признаем, что ваша реализация может наследовать от _Vector_base
, и это нормально. Так почему же std::vector
разрешено наследовать от базового класса, но вы не можете наследовать от std::vector
?
3: Вы можете наследовать от std::vector
, Если вы хотите (только будьте осторожны)
std::vector
может наследовать от _Vector_base
по тем же причинам вы можете наследовать от std::vector
: разработчики библиотеки очень осторожны (и вы должно быть тоже).
std::vector
не delete
д полиморфно с _Vector_base
указатель. Так что нам не нужно беспокоиться о ~vector()
не виртуальный. Если вы не delete
ваш унаследованный класс с полиморфным std::vector
указатель, затем ~vector()
быть не виртуальным-это не проблема. Все нормально. Не волнуйся. Давай не будем об этом. Нет полиморфных delete
s означает, что нам не нужно беспокоиться о том, что деструкторы являются виртуальными или нет.
но помимо этого, реализации библиотеки убедились, что std::vector
никогда отрезанный с помощью _Vector_base
ссылка. То же самое для вас: если вы можете гарантировать, что ваш пользовательский векторный класс никогда не нарезается (кем-то, использующим std::vector
ссылка на него), тогда Вы тоже в порядке. Отсутствие нарезки означает, что нам не нужно беспокоиться о плохих копиях.
4: Почему вы не должны наследовать от std::vector
на это было много ответов в других вопросах, но я скажу это снова здесь (и с фокусом целое _Vector_base
проблема наследования): вы (вероятно) не должны наследовать от std::vector
.
проблема в том, что если вы это сделаете, кто-то, используя ваш пользовательский векторный класс, может полиморфно удалить его (у них может быть std::vector
указатель на него, а delete
это, что плохо, если они это сделают). Или у них может быть std::vector
ссылка на ваш пользовательский объект и попытаться сделать его копию (что бы нарезать объект, что также, вероятно, было бы плохо) (я продолжаю предполагать std::vector
ссылка на ваш пользовательский объект необходима, чтобы вызвать срез объекта при копировании, потому что я продолжаю предполагать, что вы достаточно осторожны, чтобы никогда случайно не срезать объект с неосторожное задание или вызов функции; вы никогда не был бы таким беспечным, я уверен, но чужой с std::vector
ссылка может быть (и да, я был немного шуточный)).
вы можете контролировать то, что вы делаете с вашим кодом. И если вы можете контролировать его (тщательно) достаточно, чтобы убедиться, что нет полиморфных delete
S или нарезка объектов, вы в порядке.
но иногда вы не можете контролировать, что другие делают с вашим кодом. Если вы работаете в команде, это может быть проблематично, если один член команды невольно делает одну из этих вещей. Вот почему я продолжаю говорить о" будь осторожен".
еще хуже, если код используется клиентом, вы действительно не можете контролировать, что они делают, и если они делают один из эти плохие вещи, вы тот, кто, вероятно, будет обвинен и поручено исправить его (получайте удовольствие от рефакторинга всего вашего кода, который раньше полагался на ваш пользовательский класс, наследующий от std::vector
) (или просто скажите клиенту, что они не могут этого сделать, но у вас, вероятно, будет сварливый клиент, который потратил время на отладку странной проблемы, с которой они не ожидали столкнуться).
ваши разработчики стандартной библиотеки C++ могут уйти с этим наследованием, хотя, потому что они могут очень хорошо контролировать вещи: никому не разрешается использовать _Vector_base
. Вы можете использовать std::vector
. Только std::vector
. Поскольку вам не разрешено никогда (когда-нибудь) использовать _Vector_base
, стандартным реализаторам библиотеки не нужно беспокоиться о нарезке объектов или полиморфном delete
s. И поскольку они очень осторожны в их реализации в контролируемой среде, все работает просто отлично.
но еще лучше, не делая предположений о том, как std::vector
реализован и вместо этого рассматривает его как (полезно) черный ящик, мы можем убедиться, что как мы используем std::vector
переносится в другие стандартные реализации библиотеки. Если вы делаете предположения о std::vector
наследование от некоторого базового класса, вы будете ограничивать переносимость кода.
потому что это незаконно для кода пользователя, чтобы попытаться уничтожить _Vector_base
, поскольку это внутренний тип stdlib. Это предотвращает любые проблемы с деструкторами. То же самое нельзя сказать о вас.
проще говоря, стандартные внутренние библиотеки являются частным случаем, как в правилах языка, так и в том, что для них разумно. Вы не можете обобщать то, что они делают с вашим собственным кодом.
просто точка данных.
В" A Tour of C++ " Bjarne Stroustrup определяет шаблон класса, производный от std::vector<T>.
цель состоит в том, чтобы иметь проверку диапазона при использовании оператора []. Все остальное делегируется базовому классу.
проблема, которую пытается решить правило "никогда не наследовать от типа без виртуального деструктора", заключается в следующем:
- всякий раз, когда вы
delete
объект без виртуального деструктора, вызываемый деструктор не зависит от динамического типа.
То есть, если объявленный тип указателя, который выdelete
отличается от динамического типа объекта, неправильный деструктор вызывается.
запрещение подклассов типов без a виртуальный деструктор эквивалентен ограничению этих типов автономными классами: классами без родительского и без производных подклассов. Очевидно, что объявленный тип указателя на один из этих классов никогда не может отличаться от динамического типа объекта, поэтому нет опасности вызова неправильного деструктора.
однако это правило слишком строгое: следует избегать вызова неправильного деструктора, а не подкласса самого по себе. Вот класс иерархия, которая не позволяет вызывать неправильный деструктор:
class Foo {
protected:
Foo();
~Foo();
};
class Bar : public Foo {
public:
Bar();
~Bar();
};
С этими двумя классами, вполне возможно создать Bar
объект и манипулировать им с помощью Foo
указатель или ссылка, однако, a Foo*
нельзя использовать для уничтожения объекта-класса не instanciable или destructable от других код.
теперь вернемся к std::vector<>
. Класс!--8--> это тот, который предназначен для использования, и он не имеет виртуального деструктор. Но это не требует, чтобы не было базового класса для std::vector<>
. Библиотека может свободно реализовывать std::vector<>
разделив его реализацию на два класса, следуя шаблону Foo
и Bar
выше. Единственное, чего следует избегать, это то, что пользователь использует указатель базового класса для уничтожения производного объекта. Конечно, типа _Vector_base
является частным для вашей стандартной реализации библиотек, и пользователи никогда не должны использовать его, так что все в порядке.
однако, подклассы std::vector<>
это совсем другая история: деструктор std::vector<>
is public, поэтому вы не можете остановить пользователей класса
template <class T> class MyVector : public std::vector<T> {
...
};
использовать указатель базового класса, который они могут получить, чтобы уничтожить MyVector<>
. Вы можете использовать частное или защищенное наследование, чтобы избежать проблемы, но это не дает преимуществ публичного наследования. Так что, да, вы никогда не должны подкласс std::vector<>
хотя это прекрасно подходит для std::vector<>
наследовать от другого частного класс.
ответ состоит из 2 частей:
, потому что
_Vector_base
знал он должен был унаследоватьstd::vector
и был разработан как таковойесть исключение из каждого правила. Если наследуется от
std::vector
имеет смысл в вашем случае тогда просто делай то, что имеет смысл.
нет такого правила "не наследовать от базовых классов с не виртуальными деструкторами". Если есть правило, оно будет:"если у вас есть хотя бы один виртуальный метод, сделайте свой деструктор виртуальным, что также означает, что не наследуйте от базовых классов с не виртуальными деструкторами". IMHO нормально наследовать контейнеры stl, если вы следуете этому.
похоже, что некоторые архитекторы компилятора также согласны с этим. Существует буквально предупреждение, которое гласит Это-для справки:какое отношение имеет виртуальный метод. .. но предупреждение не виртуального деструктора означает во время компиляции C++?