Ты не должен наследовать от std:: vector



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



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



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



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



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



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


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



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

804   12  

12 ответов:

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

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

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

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

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

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

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

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

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

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

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> 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();
};

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

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

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

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

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

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

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

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

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

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

если вы следуете хорошему стилю 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 или unique_ptr. Короче говоря, не delete все, что вы не получили напрямую от new.

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

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

но вы можете получить в частном порядке от 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;
    ...

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

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

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

спасибо!

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

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

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

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

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

  1. позвольте мне добавить еще один способ упаковки std::vector без написания большого количества функциональных оболочек.

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. наследование от std:: span вместо std::vector и избежать проблемы dtor.

Comments

    Ничего не найдено.