Не наследуйте от std::vector

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

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

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

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

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

std::vector<int>* p  = new MyVector

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

Хорошо, я сказал свое дело. Я согрешил. Теперь это вам, чтобы простить меня или нет:)

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

  class AdVector: private std:: vector <double>
{   typedef double T;   typedef std:: vector <double> вектор;
общественности:   с использованием vector:: push_back;   использование vector:: operator [];   использование vector:: begin;   использование vector:: end;   AdVector operator * (const AdVector &) const;   AdVector operator + (const AdVector &) const;   AdVector();   virtual ~ AdVector();
};
Код>

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

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

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> 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;
    ...

Ответ 9

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

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

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

Спасибо!

Ответ 10

Если вы придерживаетесь хорошего стиля С++, отсутствие виртуальной функции не проблема, но нарезка (см. fooobar.com/questions/11610/...)

Почему отсутствие виртуальных функций не проблема? Поскольку функция не должна пытаться 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.
    }

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

Ответ 11

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

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

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

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