Ты не будешь наследовать от std :: vector - PullRequest
177 голосов
/ 04 декабря 2010

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

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

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

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

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

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

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

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

Ответы [ 12 ]

150 голосов
/ 04 декабря 2010

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

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

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

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

86 голосов
/ 04 декабря 2010

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

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

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

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

48 голосов
/ 05 декабря 2010

Основной причиной не публичного наследования от std::vector является отсутствие виртуального деструктора, который эффективно предотвращает полиморфное использование потомков. В частности, вам не разрешено до 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();
};

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

34 голосов
/ 04 декабря 2010

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

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

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

19 голосов
/ 04 декабря 2010

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

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

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

12 голосов
/ 04 декабря 2010

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

6 голосов
/ 30 августа 2014

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

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

3 голосов
/ 09 сентября 2015

Если вы следуете хорошему стилю 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.

3 голосов
/ 04 декабря 2010

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

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

Но вы можете без проблем извлечь из 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;
    ...
2 голосов
/ 23 января 2011

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

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

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

Спасибо!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...