2010-08-30 2 views
2

Один из моих классов C++ происходит от std::vector, так что он может действовать как контейнер, который также выполняет пользовательские действия над своим контентом. К сожалению, компилятор жалуется на то, что деструктор не является виртуальным, которого я не могу изменить, поскольку он находится в стандартной библиотеке.base class 'class std :: vector <...>' имеет не виртуальный деструктор

Я делаю все неправильно (вы не должны выходить из STL), или есть что-то, что я мог бы сделать, чтобы компилятор был счастлив? (Аппарт от остановки с помощью -WeffC++ :)

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

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

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

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

+12

В значительной степени «вы не получите« публично »из стандартных контейнеров библиотеки». См. [Существует ли какой-либо реальный риск для получения контейнеров C++ STL?] (Http://stackoverflow.com/questions/922248/is-there-any-real-risk-to-deriving-from-the-c-stl -контейнеры) для обсуждения этой темы (этот вопрос можно считать дубликатом этого вопроса, они по крайней мере похожи) –

ответ

10

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

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

Если вы хотите определить дополнительные операции над векторами, то для каких свободных функций в C++. Что так здорово в синтаксисе Member-call .? Большая часть <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);), то вы входите в территорию «не будьте». Стандартные библиотеки вам действительно не помогут.

+0

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

+0

Это не разрешает мое предупреждение о компиляторе. – To1ne

+0

@ To1ne: если бы вы выполнили какие-либо из моих различных предложений, то вы больше не будете использовать 'vector' в качестве общедоступного базового класса. подумайте, что предупреждение компилятора должно исчезнуть, хотя, возможно, это не относится к частному наследованию. Если ваш компилятор настаивает на том, что использование 'vector' в качестве базового класса - плохая идея, даже в этом случае, обратитесь к своей документации о том, как заставить замолчать это предупреждение. –

16

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

+0

или определить дополнительную функциональность как функции, не являющиеся членами. – jalf

1

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

+0

имел веские основания не желать композиции, когда я писал код, но точка, которую вы (и другие) подняли, очевидно, действительна. Я возвращаюсь к своему коду и изучаю вариант «правильного» способа. – PypeBros

1

я делаю все это неправильно

Возможно.

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

Делать это вместо вывода из vector обеспечивает многочисленные преимущества:

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

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

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

4) Это в целом более простой дизайн и, следовательно, менее подвержен дефектам и более прост в обслуживании.

+0

Полученный класс не касается алгоритмов векторной манипуляции, а просто добавляет некоторую информацию, такую ​​как «ширина/высота элемента» для вектора изображений одинакового размера. Спасибо, что разделили мудрость. – PypeBros