базовый класс 'класс std :: vector <...>' имеет не виртуальный деструктор - PullRequest
2 голосов
/ 30 августа 2010

Один из моих классов C ++ является производным от std::vector, так что он может действовать как контейнер, который также выполняет настраиваемые действия над своим содержимым.К сожалению, компилятор жалуется на то, что деструктор не является виртуальным, что я не могу изменить, потому что он находится в стандартной библиотеке.Я мог бы сделать, чтобы компилятор был счастлив?(начать с остановки, используя -Weffc ++:)

edit : производный класс не затрагивает алгоритмы векторной манипуляции, а просто добавляет некоторую информацию, такую ​​как "ширина / высота элемента" длявектор изображений.Например, вы могли бы подумать о

class PhotoAlbum: public std::vector<Photo> {
    String title;
    Date from_time, to_time;
    // accessors for title and dates
    void renderCover(Drawable &surface);
};

, где вы думаете о фотоальбоме в основном как о коллекции изображений с некоторыми метаданными (заголовок и время) и специфическими для альбома функциями, такими как рендеринг миниатюрнекоторых фотографий на поверхность, чтобы сделать обложку альбома.Имхо, фотоальбом IS-A коллекция Photo, больше, чем он имеет такую ​​коллекцию.

Я не вижу никакой выгоды, которую я получил бы от использования getPhotoVector() метод в PhotoAlbum это будет иметь дополнительное поле "коллекции".

Ответы [ 4 ]

16 голосов
/ 30 августа 2010

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

10 голосов
/ 30 августа 2010

Может быть безопасно иметь открытый базовый класс с не виртуальным деструктором , но поведение не определено, если кто-то выделяет экземпляр вашего класса с new, ссылается на него с vector<...>*, а затем удаляет его, используя этот указатель, без приведения его обратно к указателю на ваш класс.Таким образом, пользователи вашего класса должны знать, чтобы не делать этого.Самый надежный способ остановить их - не дать им такую ​​возможность, поэтому предупреждение компилятора.

Чтобы справиться с этой проблемой без необходимости навязывать такие странные условия вашим пользователям, лучший совет - для открытых базовых классов.в C ++ деструктор должен быть либо общедоступным и виртуальным, либо защищенным и не виртуальным (http://www.gotw.ca/publications/mill18.htm, рекомендация № 4).Поскольку деструктор std::vector не является ни тем, ни другим, это означает, что его не следует использовать в качестве общедоступного базового класса.

Если все, что вам нужно, это определить некоторые дополнительные операции над векторами, то для этого нужны свободные функциив C ++.Что же такого хорошего в синтаксисе вызова участника .?Большая часть <algorithm> состоит из дополнительных операций над вектором и другими контейнерами.

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

#include <vector>
#include <iostream>
#include <stdexcept>

class myvec : private std::vector<int> {
    size_t max_size;
  public:
    myvec(size_t m) : max_size(m) {}
    // ... other constructors

    void push_back(int i) {
        check(size()+1);
        std::vector<int>::push_back(i);
    }
    // ... other modified functions

    using std::vector<int>::operator[];
    // ... other unmodified functions

  private:
    void check(size_t newsize) {
        if (newsize > max_size) throw std::runtime_error("limit exceeded");
    }
};

int main() {
    myvec m(1);
    m.push_back(3);
    std::cout << m[0] << "\n";
    m.push_back(3); // throws an exception
}

Вы все равно должны быть осторожны, хоть.Стандарт C ++ не гарантирует, какие функции vector вызывают друг друга и каким образом.Там, где эти вызовы происходят, мой базовый класс vector не может вызвать перегрузку в myvec, поэтому мои измененные функции просто не будут применяться - это не виртуальные функции для вас.Я не могу просто перегрузить resize() в myvec и покончить с этим, мне нужно перегрузить каждую функцию, которая меняет размер, и заставить их все вызывать check (напрямую или вызывая друг друга).

Вы можете сделать вывод из ограничений в стандарте, что некоторые вещи невозможны: например, operator[] не может изменить размер вектора, поэтому в моем примере я могу безопасно использовать реализацию базового класса, и янужно только перегружать функции, которые могут изменить размер.Но стандарт не обязательно будет предоставлять гарантии такого рода для всех мыслимых производных классов.

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

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

Есливы на самом деле хотите динамический полиморфизм со стандартными контейнерами (то есть вы хотите сделать vector<int> *ptr = new myvec(1);), тогда вы входите в территорию "ты не должен".Стандартные библиотеки не могут вам помочь.

1 голос
/ 30 августа 2010

Я все делаю неправильно

Возможно.

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

Делая это вместо получения из vector, вы получаете несколько преимуществ:

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

2) Это больше соответствует «STL-пути»делать вещи.Это облегчает поддержание в будущем людьми, которые знают STL, но могут не знать ваш специальный класс, который ведет себя не так, как коллекции STL.

3) Он более расширяем.Правильно написанный функтор не заботится о том, на какую коллекцию он действует.Если по какой-то причине вы когда-нибудь захотите использовать list вместо vector, если вы используете функторы, реорганизовать намного проще, чем переопределить новый специальный класс list.*

4) В целом это более простая конструкция, поэтому она менее подвержена дефектам и проще в обслуживании.

1 голос
/ 30 августа 2010

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

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