Ответ 1
Я предполагаю, что философия С++ не оплачивает функции, которые вы не используете. В зависимости от платформы указатель на виртуальную таблицу может быть дорогостоящей ценой, если вам не нужен виртуальный деструктор.
Кто-нибудь знает, почему контейнеры STL не имеют виртуальных деструкторов?
Насколько я могу судить, единственными преимуществами являются:
Недостатком является то, что небезопасно подклассифицировать контейнеры обычным способом.
EDIT: Возможно, мой вопрос можно перефразировать: "Почему не были контейнеры STL, предназначенные для наследования?"
Поскольку они не поддерживают наследование, каждый из них придерживается следующих вариантов, когда требуется новый контейнер, для которого требуется функция STL, а также небольшое количество дополнительных функций (скажем, специализированный конструктор или новые аксессоры со значениями по умолчанию для карта или что-то еще):
В качестве побочного вопроса: существует ли безопасный способ подкласса с не виртуальными деструкторами (допустим, что я не хочу переопределять какие-либо методы, просто чтобы добавить новые)? Мое впечатление, что нет универсального и безопасного способа сделать это, если у вас нет возможности изменить код, определяющий не виртуальный класс.
ИЗМЕНИТЬ 2:
Как @doc указывает, объявления С++ 11 fancier using
несколько снижают стоимость композиции.
Я предполагаю, что философия С++ не оплачивает функции, которые вы не используете. В зависимости от платформы указатель на виртуальную таблицу может быть дорогостоящей ценой, если вам не нужен виртуальный деструктор.
Виртуальный деструктор полезен только для сценариев наследования. Контейнеры STL не предназначены для унаследованных (и не поддерживаемых сценариев). Следовательно, у них нет виртуальных деструкторов.
Я думаю, что Страуструп косвенно ответил на этот вопрос в своей фантастической статье: Почему С++ - это не просто объектно-ориентированный язык программирования:
7 Заключительные замечания
Являются ли различные представленные выше объектно-ориентированный или нет? Какие? Используя какое определение объектно-ориентированный? В большинстве случаев я подумайте, что это неправильные вопросы. Важно то, какие идеи вы можете четко выразить, как легко вы можете комбинировать программное обеспечение от разных источников, а также эффективности и поддерживаемые результирующие программы находятся. Другими словами, как вы поддерживаете хорошие методы программирования и хорошие методы проектирования важны ярлыки и звуковые слова. Основные идея состоит в том, чтобы улучшить дизайн и программирование через абстракцию. Вы хотите скрыть подробности, вы хотите использовать любую общность в системе, и вы хотите сделать это доступным. Я хотел бы призвать вас не делать объективно ориентированным бессмысленным срок. Понятие "объектно ориентированное" слишком часто обесценивается- by приравнивая его хорошим,
- путем приравнивания это с одним языком или
- by Принимая все как объектно.
Я утверждал, что есть - и должны быть - полезны методы вне объектно ориентированных программирования и дизайна. Однако для избегать совершенно непонятного, я хотел бы подчеркнуть, что я не попытался бы серьезный проект используя язык программирования, который по крайней мере, не поддержали классический понятие объектно-ориентированного программирования. В дополнение к средствам, которые поддерживают объектно-ориентированное программирование, я хочу - и С++ - функции, которые идут помимо тех, кто их поддерживает прямое выражение понятий и отношения.
STL был построен с использованием трех концептуальных инструментов. Общее программирование + Функциональный стиль + Абстракция данных == Стиль STL. Не странно, что ООП - это не лучший способ представить библиотеку данных и алгоритмов. Хотя ООП используется в других частях стандартной библиотеки, разработчик STL видел, что сочетание трех упомянутых методов лучше, чем ООП. Короче говоря, библиотека не была разработана с учетом ООП, а на С++, если вы ее не используете, она не входит в комплект с вашим кодом. Вы не платите за то, что не используете. Классы std::vector, std:: list,... представляют собой понятия не ООП в смысле Java/С#. Это просто абстрактные типы данных в наилучшей интерпретации.
Почему не были контейнеры STL, предназначенные для наследования?
По моему скромному мнению, они есть. Если бы они этого не сделали, они были бы окончательными. И когда я смотрю в источник stl_vector.h
, я вижу, что моя реализация STL использует защищенное наследование _Vector_base<_Tp, _Alloc>
для предоставления доступа для производных классов:
template<typename _Tp, typename _Alloc = allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
Не будет ли он использовать частное наследование, если подкласс не приветствуется?
существует безопасный способ подкласса с не виртуальными деструкторами (допустим, что я не хочу переопределять какие-либо методы, просто чтобы добавить новые)
Почему бы не использовать наследование protected
или private
и показать желаемую часть интерфейса с ключевым словом using
?
class MyVector : private std::vector<int>
{
typedef std::vector<int> Parent;
public:
using Parent::size;
using Parent::push_back;
using Parent::clear;
//and so on + of course required ctors, dtors and operators.
};
Этот подход гарантирует, что пользователь класса не будет упускать экземпляр std::vector<int>
, и он безопасен, поскольку единственная проблема с не виртуальным деструктором заключается в том, что он не будет вызывать производный, когда объект будет удален как экземпляр родительского класса.
... У меня также есть идея, что вы даже можете наследовать публично, если ваш класс не имеет деструктора. Ересь?
Как уже указывалось, контейнеры STL не предназначены для наследования. Нет виртуальных методов, все члены данных являются частными, не защищеными getters/seters/helpers. И как вы обнаружили, нет виртуальных деструкторов.
Я бы предположил, что вы действительно должны использовать контейнеры через композицию, а не наследование реализации, как "has-a", а не "is-a".
вы не должны слепо добавлять виртуального деструктора в каждый класс. Если бы это было так, язык не разрешил бы вам другой вариант. Когда вы добавляете виртуальный метод в класс, который не имеет других виртуальных методов, вы просто увеличиваете размер экземпляров класса по размеру указателя, как правило, 4 байта. Это дорого в зависимости от того, что вы делаете. Увеличение размера происходит из-за того, что создается виртуальная таблица для хранения списка виртуальных методов, и каждый экземпляр нуждается в указателе на v-таблицу. Он обычно находится в первой ячейке экземпляра.
Другим решением для подкласса из контейнеров STL является то, которое дает Бо Цянь, используя интеллектуальные указатели.
Расширенный С++: Виртуальный деструктор и интеллектуальный деструктор
class Dog {
public:
~Dog() {cout << "Dog is destroyed"; }
};
class Yellowdog : public Dog {
public:
~Yellowdog() {cout << "Yellow dog destroyed." << endl; }
};
class DogFactory {
public:
static shared_ptr<Dog> createYellowDog() {
return shared_ptr<Yellowdog>(new Yellowdog());
}
};
int main() {
shared_ptr<Dog> pd = DogFactory::createYellowDog();
return 0;
}
Это полностью исключает dillema с виртуальными деструкторами.
Если вам действительно нужен виртуальный деструктор, вы можете добавить его в класс, полученный из vector < > , а затем использовать этот класс в качестве базового класса везде, где вам нужен виртуальный интерфейс. Делая это, компилятор вызовет виртуальный деструктор из вашего базового класса, который, в свою очередь, вызовет не виртуальный деструктор из векторного класса.
Пример:
#include <vector>
#include <iostream>
using namespace std;
class Test
{
int val;
public:
Test(int val) : val(val)
{
cout << "Creating Test " << val << endl;
}
Test(const Test& other) : val(other.val)
{
cout << "Creating copy of Test " << val << endl;
}
~Test()
{
cout << "Destructing Test " << val << endl;
}
};
class BaseVector : public vector<Test>
{
public:
BaseVector()
{
cout << "Creating BaseVector" << endl;
}
virtual ~BaseVector()
{
cout << "Destructing BaseVector" << endl;
}
};
class FooVector : public BaseVector
{
public:
FooVector()
{
cout << "Creating FooVector" << endl;
}
virtual ~FooVector()
{
cout << "Destructing FooVector" << endl;
}
};
int main()
{
BaseVector* ptr = new FooVector();
ptr->push_back(Test(1));
delete ptr;
return 0;
}
Этот код дает следующий результат:
Creating BaseVector
Creating FooVector
Creating Test 1
Creating copy of Test 1
Destructing Test 1
Destructing FooVector
Destructing BaseVector
Destructing Test 1
Ни один виртуальный деструктор не позволяет классу быть подклассами правильно.