Нельзя наследовать от std :: vector

Хорошо, это действительно трудно признать, но на данный момент у меня есть сильный соблазн унаследовать от std::vector .

Мне нужно около 10 индивидуальных алгоритмов для вектора, и я хочу, чтобы они были непосредственно членами вектора. Но, естественно, я хочу также иметь остальную часть интерфейса std::vector . Ну, моя первая идея, как законопослушный гражданин, состояла в том, чтобы иметь член std::vector в classе MyVector . Но тогда мне придется вручную упрекнуть весь интерфейс std :: vector. Слишком много, чтобы печатать. Затем я подумал о частном наследовании, чтобы вместо того, чтобы перепрограммировать методы, я бы написал кучу using std::vector::member в публичном разделе. На самом деле это утомительно.

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

Является ли мое решение абсолютно неоправданным? Если да, то почему? Можете ли вы предоставить альтернативу, которая будет иметь дополнительные члены на самом деле, но не будет включать повторный набор всего интерфейса вектора? Я в этом сомневаюсь, но если сможешь, я просто буду счастлив.

Кроме того, помимо того, что какой-то идиот может написать что-то вроде

 std::vector* p = new MyVector 

есть ли другая реальная опасность при использовании MyVector? Говоря реалистично, я отбрасываю такие вещи, как представить себе функцию, которая принимает указатель на вектор …

Ну, я сказал свое дело. Я согрешил. Теперь вам решать, простите меня или нет 🙂

На самом деле, нет ничего плохого в публичном наследовании std::vector . Если вам это нужно, просто сделайте это.

Я бы предложил сделать это, только если это действительно необходимо. Только если вы не можете делать то, что хотите, со свободными функциями (например, должны поддерживать некоторое состояние).

Проблема в том, что MyVector – это новый объект. Это означает, что новый разработчик C ++ должен знать, каково это, прежде чем использовать его. В чем разница между std::vector и MyVector ? Какой из них лучше использовать здесь и там? Что делать, если мне нужно переместить std::vector в MyVector ? Могу я просто использовать swap() или нет?

Не создавайте новые объекты, чтобы сделать что-то лучше. Эти сущности (особенно такие общие) не будут жить в вакууме. Они будут жить в смешанной среде с постоянно увеличивающейся энтропией.

Вся STL была разработана таким образом, что алгоритмы и контейнеры являются отдельными .

Это привело к понятию разных типов iteratorов: константные iteratorы, iteratorы произвольного доступа и т. Д.

Поэтому я рекомендую вам принять это соглашение и разработать свои алгоритмы таким образом, чтобы им было все равно, каков контейнер, над которым они работают, и им потребуется только определенный тип iteratorа, который им нужно будет выполнить операции.

Кроме того, позвольте мне перенаправить вас на некоторые хорошие замечания Джеффа Этвуда .

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

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

 class AdVector: private std::vector { typedef double T; typedef std::vector vector; public: using vector::push_back; using vector::operator[]; using vector::begin; using vector::end; AdVector operator*(const AdVector & ) const; AdVector operator+(const AdVector & ) const; AdVector(); virtual ~AdVector(); }; 

Сначала вы должны рассмотреть возможность реорганизации своих алгоритмов для абстрагирования от типа контейнера, в котором они работают, и оставить их в виде бесплатных шаблонных функций, как указано большинством респондентов. Обычно это делается путем принятия алгоритмом пары iteratorов вместо контейнера в качестве аргументов.

Если вы рассматриваете это, вы явно уже убили языковых педантов в своем офисе. С их стороны, почему бы просто не сделать

 struct MyVector { std::vector v; // public! void func1( ... ) ; // and so on } 

Это позволит обойти все возможные ошибки, которые могут возникнуть из-за непредсказуемости вашего classа MyVector, и вы все равно можете получить доступ ко всем векторным операциям, просто добавив немного .v .

Что вы надеетесь достичь? Просто предоставляете некоторые функции?

Идиоматический способ C ++ – это просто написать некоторые бесплатные функции, реализующие функциональность. Скорее всего, вам действительно не нужен std :: vector, особенно для функциональности, которую вы реализуете, а это означает, что вы фактически теряете возможность повторного использования, пытаясь наследовать от std :: vector.

Я бы настоятельно посоветовал вам взглянуть на стандартную библиотеку и заголовки и размышлять над тем, как они работают.

Я думаю, что очень немногие правила должны соблюдаться слепо 100% времени. Похоже, вы много думали и убеждены, что это путь. Итак, если кто-то не придумает особых причин, чтобы не делать этого, я думаю, вы должны продолжить свой план.

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

Причины второго варианта могут быть только идеологическими, потому что std::vector s не являются полиморфными, и в противном случае нет никакой разницы, открываете ли вы публичный интерфейс std::vector через публичное наследование или через публичное членство. (Предположим, вам нужно сохранить какое-то состояние в своем объекте, чтобы вы не могли избавиться от бесплатных функций). На менее обоснованной ноте и с идеологической точки зрения оказывается, что std::vector s – это своего рода «простая идея», поэтому любая сложность в виде объектов разных возможных classов на их месте идеологически не используется.

Если вы придерживаетесь хорошего стиля C ++, отсутствие виртуальной функции – это не проблема, а нарезка (см. https://stackoverflow.com/a/14461532/877329 )

Почему отсутствие виртуальных функций не проблема? Поскольку функция не должна пытаться delete какой-либо указатель, который она получает, так как она не имеет права собственности на нее. Поэтому, если следовать строгим правилам владения, виртуальные деструкторы не нужны. Например, это всегда неправильно (с виртуальным деструктором или без него):

 void foo(SomeType* obj) { if(obj!=nullptr) //The function prototype only makes sense if parameter is optional { obj->doStuff(); } delete obj; } class SpecialSomeType:public SomeType { // whatever }; int main() { SpecialSomeType obj; doStuff(&obj); //Will crash here. But caller does not know that // ... } 

Напротив, это всегда будет работать (с виртуальным деструктором или без него):

 void foo(SomeType* obj) { if(obj!=nullptr) //The function prototype only makes sense if parameter is optional { obj->doStuff(); } } class SpecialSomeType:public SomeType { // whatever }; int main() { SpecialSomeType obj; doStuff(&obj); // The correct destructor *will* be called here. } 

Если объект создается на заводе, фабрика также должна вернуть указатель на рабочий деаэратор, который следует использовать вместо delete , поскольку фабрика может использовать свою собственную кучу. Вызывающий может получить форму share_ptr или share_ptr . Короче говоря, не delete ничего, что вы не получили напрямую от new .

На практике: если у вас нет данных в производном classе, у вас нет никаких проблем, даже при полиморфном использовании. Вам нужен только виртуальный деструктор, если размеры базового classа и производного classа различны и / или у вас есть виртуальные функции (что означает v-таблицу).

НО в теории: From [expr.delete] в C ++ 0x FCD: в первом альтернативе (удалить объект), если статический тип подлежащего удалению объекта отличается от его динамического типа, статический тип должен быть базовый class динамического типа объекта, подлежащего удалению, и статический тип должен иметь виртуальный деструктор или поведение не определено.

Но вы можете получить конфиденциально из std :: vector без проблем. Я использовал следующий шаблон:

 class PointVector : private std::vector { typedef std::vector Vector; ... using Vector::at; using Vector::clear; using Vector::iterator; using Vector::const_iterator; using Vector::begin; using Vector::end; using Vector::cbegin; using Vector::cend; using Vector::crbegin; using Vector::crend; using Vector::empty; using Vector::size; using Vector::reserve; using Vector::operator[]; using Vector::assign; using Vector::insert; using Vector::erase; using Vector::front; using Vector::back; using Vector::push_back; using Vector::pop_back; using Vector::resize; ... 

Я также унаследовал от std::vector недавно и нашел, что это очень полезно, и до сих пор у меня не было никаких проблем с ним.

Мой class – это разреженный матричный class, а это значит, что мне нужно где-то хранить элементы матрицы, а именно в std::vector . Моей причиной для наследования было то, что я был слишком ленив, чтобы писать интерфейсы ко всем методам, а также я соединяю class с Python через SWIG, где уже есть хороший код интерфейса для std::vector . Мне было намного проще расширить этот код интерфейса до моего classа, а не писать новый с нуля.

Единственная проблема, которую я вижу с этим подходом, – это не столько с не виртуальным деструктором, сколько с некоторыми другими методами, которые я бы хотел перегрузить, например, push_back() , resize() , insert() и т. Д. Частное наследование действительно может быть хорошим вариантом.

Благодаря!

Да, это безопасно, пока вы стараетесь не делать то, что небезопасно … Я не думаю, что когда-либо видел, что кто-то использует вектор с новым, поэтому на практике вы, вероятно, будете в порядке. Однако это не обычная идиома в c ++ ….

Можете ли вы дать больше информации о том, что такое алгоритмы?

Иногда вы заканчиваете спуск по одной дороге с дизайном, а затем не видите другие пути, которые вы могли бы сделать – тот факт, что вы утверждаете, что вам нужен вектор с 10 новыми алгоритмами, звонит мне звонками – есть ли действительно 10 общих целей алгоритмы, которые может реализовать вектор, или вы пытаетесь создать объект, который является и универсальным вектором И, который содержит конкретные функции приложения?

Я, конечно, не говорю, что вы не должны этого делать, это просто то, что информация, которую вы подавали, сигнализирует, звонит, что заставляет меня думать, что, возможно, что-то не так с вашими абстракциями, и есть лучший способ добиться того, что вы хотеть.

Здесь, позвольте мне представить еще два способа сделать, чего вы хотите. Один из них – это еще один способ обернуть std::vector , другой способ наследовать, не давая пользователям шанс что-либо сломать:

  1. Позвольте мне добавить еще один способ обертывания std::vector без написания множества оберток функций.

 #include  // For std:: forward struct Derived: protected std::vector { // Anything... using underlying_t = std::vector; auto* get_underlying() noexcept { return static_cast(this); } auto* get_underlying() const noexcept { return static_cast(this); } template  auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args) { return (get_underlying()->*member_f)(std::forward(args)...); } }; 
  1. Наследование из std :: span вместо std::vector и избежание проблемы dtor.
  • Что такое * так * неправильно с наследованием classа case?
  • Вызов метода подclassа из суперclassа
  • Использовать композицию над наследованием
  • Наследование и зависимость
  • Почему мы иногда вызываем супер в Android?
  • Одиночное наследование таблиц и где их использовать в Rails
  • Разница между частным, общественным и защищенным наследованием
  • Метод базового вызова Java из базового конструктора
  • Почему в Java нет множественного наследования, но допускается реализация нескольких интерфейсов?
  • Полученный доступ к шаблону classа к элементам-членам базового classа
  • Наследование наследования проекта Maven - нужно ли указывать родительскую версию?
  • Давайте будем гением компьютера.