gcc4.9.Реализация 2 в libstdc++ std:: vector наследуется от базы векторов (не виртуального дестуктора). Почему это нормально? [дубликат]

этот вопрос уже есть ответ здесь:

поэтому я некоторое время использовал контейнер, полученный из 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() быть не виртуальным-это не проблема. Все нормально. Не волнуйся. Давай не будем об этом. Нет полиморфных deletes означает, что нам не нужно беспокоиться о том, что деструкторы являются виртуальными или нет.

но помимо этого, реализации библиотеки убедились, что std::vector никогда отрезанный с помощью _Vector_base ссылка. То же самое для вас: если вы можете гарантировать, что ваш пользовательский векторный класс никогда не нарезается (кем-то, использующим std::vector ссылка на него), тогда Вы тоже в порядке. Отсутствие нарезки означает, что нам не нужно беспокоиться о плохих копиях.

4: Почему вы не должны наследовать от std::vector

на это было много ответов в других вопросах, но я скажу это снова здесь (и с фокусом целое _Vector_base проблема наследования): вы (вероятно) не должны наследовать от std::vector.

проблема в том, что если вы это сделаете, кто-то, используя ваш пользовательский векторный класс, может полиморфно удалить его (у них может быть std::vector указатель на него, а delete это, что плохо, если они это сделают). Или у них может быть std::vector ссылка на ваш пользовательский объект и попытаться сделать его копию (что бы нарезать объект, что также, вероятно, было бы плохо) (я продолжаю предполагать std::vector ссылка на ваш пользовательский объект необходима, чтобы вызвать срез объекта при копировании, потому что я продолжаю предполагать, что вы достаточно осторожны, чтобы никогда случайно не срезать объект с неосторожное задание или вызов функции; вы никогда не был бы таким беспечным, я уверен, но чужой с std::vector ссылка может быть (и да, я был немного шуточный)).

вы можете контролировать то, что вы делаете с вашим кодом. И если вы можете контролировать его (тщательно) достаточно, чтобы убедиться, что нет полиморфных deleteS или нарезка объектов, вы в порядке.

но иногда вы не можете контролировать, что другие делают с вашим кодом. Если вы работаете в команде, это может быть проблематично, если один член команды невольно делает одну из этих вещей. Вот почему я продолжаю говорить о" будь осторожен".

еще хуже, если код используется клиентом, вы действительно не можете контролировать, что они делают, и если они делают один из эти плохие вещи, вы тот, кто, вероятно, будет обвинен и поручено исправить его (получайте удовольствие от рефакторинга всего вашего кода, который раньше полагался на ваш пользовательский класс, наследующий от std::vector) (или просто скажите клиенту, что они не могут этого сделать, но у вас, вероятно, будет сварливый клиент, который потратил время на отладку странной проблемы, с которой они не ожидали столкнуться).

ваши разработчики стандартной библиотеки C++ могут уйти с этим наследованием, хотя, потому что они могут очень хорошо контролировать вещи: никому не разрешается использовать _Vector_base. Вы можете использовать std::vector. Только std::vector. Поскольку вам не разрешено никогда (когда-нибудь) использовать _Vector_base, стандартным реализаторам библиотеки не нужно беспокоиться о нарезке объектов или полиморфном deletes. И поскольку они очень осторожны в их реализации в контролируемой среде, все работает просто отлично.

но еще лучше, не делая предположений о том, как 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 частей:

  1. , потому что _Vector_base знал он должен был унаследовать std::vector и был разработан как таковой

  2. есть исключение из каждого правила. Если наследуется от std::vector имеет смысл в вашем случае тогда просто делай то, что имеет смысл.


нет такого правила "не наследовать от базовых классов с не виртуальными деструкторами". Если есть правило, оно будет:"если у вас есть хотя бы один виртуальный метод, сделайте свой деструктор виртуальным, что также означает, что не наследуйте от базовых классов с не виртуальными деструкторами". IMHO нормально наследовать контейнеры stl, если вы следуете этому.

похоже, что некоторые архитекторы компилятора также согласны с этим. Существует буквально предупреждение, которое гласит Это-для справки:какое отношение имеет виртуальный метод. .. но предупреждение не виртуального деструктора означает во время компиляции C++?