Хорошо, это действительно трудно признаться, но у меня сейчас есть сильное искушение унаследовать std::vector
.
Мне нужно около 10 индивидуальных алгоритмов для вектора, и я хочу, чтобы они были непосредственно членами вектора. Но, естественно, я хочу иметь и остальную часть std::vector
интерфейса. Ну, моя первая идея, как законопослушного гражданина, была иметь std::vector
члена в MyVector
классе. Но тогда мне пришлось бы вручную заново предоставить весь интерфейс std :: vector. Слишком много, чтобы напечатать. Затем я подумал о частном наследовании, чтобы вместо повторного предоставления методов я написал несколько записей using std::vector::member
в открытом разделе. Это слишком утомительно на самом деле.
И вот я действительно думаю, что могу просто публично наследовать std::vector
, но в документации предупреждаю, что этот класс не следует использовать полиморфно. Я думаю, что большинство разработчиков достаточно компетентны, чтобы понять, что это не должно использоваться полиморфно в любом случае.
Мое решение абсолютно неоправданно? Если так, то почему? Можете ли вы предложить альтернативу , которая будет иметь дополнительные член собственно член , но не будет включать перепечатывать все интерфейс вектора? Я сомневаюсь в этом, но если вы можете, я просто буду счастлив.
Кроме того, кроме того, что какой-то идиот может написать что-то вроде
std::vector<int>* p = new MyVector
есть ли другая реальная опасность в использовании MyVector? Говоря реалистично, я отказываюсь от таких вещей, как представить себе функцию, которая принимает указатель на вектор ...
Ну, я изложил свой случай. Я согрешил. Теперь ты должен простить меня или нет :)
std::vector
довольно большой, и когда появится C ++ 1x, он значительно расширится. Это много, чтобы напечатать и еще больше, чтобы расшириться через несколько лет. Я думаю, что это хорошая причина для рассмотрения наследования, а не сдерживания - если следовать предпосылке, что эти функции должны быть членами (в чем я сомневаюсь). Правило не выводить из контейнеров STL - они не полиморфны. Если вы не используете их таким образом, это не относится.Ответы:
На самом деле, нет ничего плохого в публичном наследовании
std::vector
. Если вам это нужно, просто сделайте это.Я бы предложил сделать это, только если это действительно необходимо. Только если вы не можете делать то, что хотите, с помощью бесплатных функций (например, должны сохранять некоторое состояние).
Проблема в том, что
MyVector
это новая сущность. Это означает, что новый разработчик C ++ должен знать, что это, черт возьми, перед его использованием. Какая разница междуstd::vector
аMyVector
? Какой из них лучше использовать здесь и там? Что делать , если мне нужно , чтобы перейтиstd::vector
кMyVector
? Могу я просто использоватьswap()
или нет?Не создавайте новые объекты, чтобы сделать что-то, чтобы выглядеть лучше. Эти сущности (особенно такие общие) не собираются жить в вакууме. Они будут жить в смешанной среде с постоянно увеличивающейся энтропией.
источник
MyVector
а затем попробуйте передать их функциям, которые принимаютstd::vector&
илиstd::vector*
. Если существует какое-либо назначение копирования, использующее std :: vector * или std :: vector &, у нас есть проблемы с нарезкой, когда новые члены-данныеMyVector
не будут скопированы. То же самое можно сказать и о вызове swap через базовый указатель / ссылку. Я склонен думать, что любая иерархия наследования, которая рискует разрезать объекты, является плохой.std::vector
деструктора нетvirtual
, поэтому никогда не наследуй егоВесь STL был разработан таким образом, что алгоритмы и контейнеры являются отдельными .
Это привело к концепции различных типов итераторов: константных итераторов, итераторов с произвольным доступом и т. Д.
Поэтому я рекомендую вам принять это соглашение и спроектировать свои алгоритмы таким образом, чтобы они не заботились о том, над каким контейнером они работают, - и им потребуется только определенный тип итератора, который им потребуется для выполнения их операции.
Кроме того, позвольте мне перенаправить вас на несколько хороших замечаний Джеффа Этвуда .
источник
Основная причина, по которой вы не наследуете
std::vector
публично, - это отсутствие виртуального деструктора, который эффективно предотвращает полиморфное использование потомков. В частности, вы не допускаются кdelete
черезstd::vector<T>*
которые на самом деле точек на производный объект (даже если производный класс не добавляет пользователей), но компилятор обычно не может предупредить вас об этом.Частное наследство разрешено на этих условиях. Поэтому я рекомендую использовать частное наследование и пересылать необходимые методы от родителя, как показано ниже.
Сначала вы должны рассмотреть возможность рефакторинга ваших алгоритмов, чтобы абстрагировать тип контейнера, с которым они работают, и оставить их в качестве свободных шаблонных функций, как указывало большинство ответчиков. Обычно это делается, заставляя алгоритм принимать пару итераторов вместо контейнера в качестве аргументов.
источник
vector
Выделенное хранилище не является проблемой - в конце концов,vector
деструктор будет вызываться через указатель наvector
. Это только то , что стандарт запрещаетdelete
ИНГ свободное хранилище объектов с помощью выражения базового класса. Несомненно, причина в том, что механизм (de) выделения может попытаться вывести размер фрагмента памяти, чтобы освободить его отdelete
операнда, например, когда есть несколько областей выделения для объектов определенных размеров. Это ограничение, на самом деле, не распространяется на обычное уничтожение объектов со статическим или автоматическим сроком хранения.Если вы обдумываете это, вы явно уже убили языковых педантов в своем офисе. С ними в стороне, почему бы просто не сделать
Это позволит обойти все возможные грубые ошибки, которые могут возникнуть в результате случайного изменения класса MyVector, и вы все равно сможете получить доступ ко всем векторным операциям, просто добавив немного
.v
.источник
Чего ты надеешься достичь? Просто предоставляя некоторую функциональность?
Идиоматический способ сделать это на C ++ - просто написать несколько бесплатных функций, реализующих эту функциональность. Скорее всего, вам на самом деле не нужен std :: vector, особенно для реализуемой вами функциональности, что означает, что вы фактически теряете возможность повторного использования, пытаясь наследовать от std :: vector.
Я настоятельно рекомендую вам взглянуть на стандартную библиотеку и заголовки и подумать о том, как они работают.
источник
front
иback
функцию тоже. :) (Также рассмотрим пример бесплатноbegin
иend
в C ++ 0x и boost.)Я думаю, что очень немногие правила должны соблюдаться вслепую в 100% случаев. Похоже, вы много об этом думали и убеждены, что это правильный путь. Так что - если кто-то не придумает веские конкретные причины не делать этого - я думаю, что вы должны продолжить свой план.
источник
Нет никаких причин наследовать,
std::vector
если только кто-то не хочет создать класс, который работает иначе, чемstd::vector
потому, что он обрабатывает свои собственные скрытые деталиstd::vector
определения, или если у человека нет идеологических причин использовать объекты такого класса вместоstd::vector
х. Однако создатели стандарта на C ++ не предоставилиstd::vector
никакого интерфейса (в форме защищенных членов), который мог бы использовать такой унаследованный класс для улучшения вектора особым образом. На самом деле у них не было возможности думать о каком-либо конкретном аспекте, который может нуждаться в расширении или тонкой настройке дополнительной реализации, поэтому им не нужно было думать о предоставлении какого-либо такого интерфейса для каких-либо целей.Причины второго варианта могут быть только идеологическими, поскольку
std::vector
s не являются полиморфными, и в противном случае нет разницы, выставляете ли выstd::vector
открытый интерфейс через публичное наследование или через публичное членство. (Предположим, вам нужно сохранить некоторое состояние в вашем объекте, чтобы вы не могли обойтись свободными функциями). На менее здравой ноте и с идеологической точки зрения кажется, чтоstd::vector
s - это своего рода «простая идея», поэтому любая сложность в виде объектов различных возможных классов на их месте идеологически не имеет смысла.источник
С практической точки зрения: если у вас нет членов данных в производном классе, у вас нет проблем, даже в полиморфном использовании. Вам нужен только виртуальный деструктор, если размеры базового класса и производного класса различны и / или у вас есть виртуальные функции (что означает v-таблицу).
НО в теории: из [expr.delete] в C ++ 0x FCD: в первом варианте (удаление объекта), если статический тип удаляемого объекта отличается от его динамического типа, статический тип должен быть базовый класс динамического типа объекта, который должен быть удален, и статический тип должен иметь виртуальный деструктор, или поведение не определено.
Но вы можете получить конфиденциально из std :: vector без проблем. Я использовал следующий шаблон:
источник
[expr.delete]
в C ++ 0x FCD: <quote> В первом варианте (удалить объект), если статический тип удаляемого объекта отличается от его динамического типа, статический тип должен быть базовым классом динамического типа. объекта, который должен быть удален, и статический тип должен иметь виртуальный деструктор или поведение не определено. </ quote>Если вы следуете хорошему стилю C ++, отсутствие виртуальной функции - не проблема, а нарезка (см. Https://stackoverflow.com/a/14461532/877329 )
Почему отсутствие виртуальных функций не проблема? Потому что функция не должна пытаться получить
delete
какой-либо указатель, который она получает, поскольку она не имеет владельца. Следовательно, если следовать строгой политике владения, виртуальные деструкторы не нужны. Например, это всегда неправильно (с виртуальным деструктором или без него):Напротив, это всегда будет работать (с виртуальным деструктором или без него):
Если объект создается фабрикой, фабрика также должна возвращать указатель на работающее средство удаления, которое следует использовать вместо
delete
, поскольку фабрика может использовать свою собственную кучу. Звонящий может получить его в видеshare_ptr
илиunique_ptr
. Короче говоря, не делаетdelete
ничего , вы не получите непосредственно отnew
.источник
Да, это безопасно, если вы осторожны, чтобы не делать то, что небезопасно ... Я не думаю, что когда-либо видел, чтобы кто-то использовал вектор с новым, так что на практике вы, вероятно, будете в порядке. Тем не менее, это не распространенная идиома в C ++ ....
Вы можете дать больше информации о алгоритмах?
Иногда вы заканчиваете тем, что идете по одной дороге с дизайном, а затем не видите других путей, которые вы, возможно, выбрали - тот факт, что вы утверждаете, что вам нужно вектор с 10 новыми алгоритмами, звонит мне в тревогу - действительно ли существует 10 общих целей? алгоритмы, которые вектор может реализовать, или вы пытаетесь создать объект, который является одновременно вектором общего назначения И который содержит специфические для приложения функции?
Я, конечно, не говорю, что вы не должны этого делать, просто из-за того, что вы дали сигнал тревоги, я думаю, что, возможно, что-то не так с вашими абстракциями, и есть лучший способ достичь того, что вы хотеть.
источник
Я также унаследовал от
std::vector
недавно, и нашел это очень полезным, и до сих пор у меня не было никаких проблем с ним.Мой класс - это класс разреженных матриц, что означает, что мне нужно где-то хранить свои матричные элементы, а именно в
std::vector
. Моя причина наследования заключалась в том, что мне было немного лень писать интерфейсы для всех методов, а также я связываю класс с Python через SWIG, где уже есть хороший интерфейсный код дляstd::vector
. Я обнаружил, что гораздо проще расширить этот код интерфейса для своего класса, чем писать новый с нуля.Единственная проблема , которую я могу видеть , с подходом не столько с невиртуальномом деструктором, а некоторые другие методы, которые я хотел бы перегрузки, такие как
push_back()
,resize()
, иinsert()
т.д. Частное наследование действительно может быть хорошим вариантом.Спасибо!
источник
Здесь, позвольте мне представить еще 2 способа сделать то, что вы хотите. Один - это другой способ переноса
std::vector
, другой - способ наследования, не дающий пользователям возможности что-либо сломать:std::vector
без написания большого количества функциональных оболочек.std::vector
и избежать проблемы dtor.источник
Этот вопрос гарантированно вызывает зловонное сцепление с жемчугом, но на самом деле нет веской причины избегать или «излишне умножать сущности», чтобы избежать вывода из стандартного контейнера. Самое простое, самое короткое из возможных выражений - самое ясное и лучшее.
Вам нужно проявлять все обычные меры предосторожности в отношении любого производного типа, но в случае базы из Стандарта нет ничего особенного. Заменить базовую функцию-член может быть сложно, но это было бы неразумно делать с любой не виртуальной базой, так что здесь особого нет. Если бы вы добавили элемент данных, вам нужно было бы беспокоиться о нарезке, если элемент должен быть согласованным с содержимым базы, но, опять же, то же самое для любой базы.
Место, где я нашел вывод из стандартного контейнера, особенно полезно, это добавить единственный конструктор, который точно выполняет необходимую инициализацию, без шансов путаницы или взлома другими конструкторами. (Я смотрю на вас, конструкторы initialization_list!) Затем вы можете свободно использовать результирующий объект, разрезать его - передать его по ссылке на что-то, ожидающее базу, перейти от него к экземпляру базы, что у вас есть. Не нужно беспокоиться о крайних случаях, если только это не мешает вам связать аргумент шаблона с производным классом.
Место, где эта техника будет сразу полезна в C ++ 20, - это резервирование. Где мы могли бы написать
мы можем сказать
а затем, даже как ученики,
(в соответствии с предпочтениями) и не нужно писать конструктор просто для того, чтобы вызывать их в Reserve ().
(
reserve_in
Технически, потому что техническая необходимость ждать C ++ 20 заключается в том, что предыдущие стандарты не требуют сохранения емкости пустого вектора при перемещениях. Это признается как недосмотр, и можно ожидать, что оно будет исправлено. как дефект во времени для 20-го. Мы также можем ожидать, что исправление будет эффективно задним числом к предыдущим Стандартам, потому что все существующие реализации действительно сохраняют емкость при перемещениях; Стандарты просто не требовали этого. в любом случае резервирование оружия - это почти всегда просто оптимизация.)Некоторые утверждают, что случай
reserve_in
лучше обслуживается бесплатным шаблоном функции:Такая альтернатива, безусловно, жизнеспособна - и даже иногда может быть бесконечно быстрее из-за * RVO. Но выбор деривации или свободной функции должен быть сделан сам по себе, а не из безосновательного (хе!) Суеверия о производных от стандартных компонентов. В приведенном выше примере только вторая форма будет работать с функцией free; хотя вне контекста класса это можно было бы написать немного более кратко:
источник