Подтвердить что ты не робот

Почему контейнеры STL не имеют виртуальных деструкторов?

Кто-нибудь знает, почему контейнеры STL не имеют виртуальных деструкторов?

Насколько я могу судить, единственными преимуществами являются:

  • он уменьшает размер экземпляра одним указателем (к таблице виртуальных методов) и
  • он делает разрушение и строительство немного быстрее.

Недостатком является то, что небезопасно подклассифицировать контейнеры обычным способом.

EDIT: Возможно, мой вопрос можно перефразировать: "Почему не были контейнеры STL, предназначенные для наследования?"

Поскольку они не поддерживают наследование, каждый из них придерживается следующих вариантов, когда требуется новый контейнер, для которого требуется функция STL, а также небольшое количество дополнительных функций (скажем, специализированный конструктор или новые аксессоры со значениями по умолчанию для карта или что-то еще):

  • Репликация композиции и интерфейса: создайте новый шаблон или класс, который владеет контейнером STL в качестве частного члена, и имеет один сквозной встроенный метод для каждого метода STL. Это так же эффективно, как и наследование, позволяет избежать стоимости таблицы виртуальных методов (в тех случаях, когда это имеет значение). К сожалению, контейнеры STL имеют довольно широкие интерфейсы, поэтому для этого требуется много строк кода для чего-то, что должно быть легко сделать.
  • Просто выполните функции: используйте голые (возможно, шаблонные) функции с файловыми областями вместо того, чтобы пытаться добавить функции-члены. В некотором смысле это может быть хорошим подходом, но преимущества инкапсуляции теряются.
  • Состав с публичным доступом STL: пусть владелец контейнера STL позволяет пользователям напрямую обращаться к самому контейнеру STL (возможно, через аксессуры). Это требует наименьшего кодирования для библиотеки, но это гораздо менее удобно для пользователей. Одна из больших точек продажи композиции заключается в том, что вы уменьшаете сцепление в коде, но это решение полностью связывает контейнер STL с контейнером владельца (потому что владелец возвращает истинный контейнер STL).
  • Полиморфизм времени компиляции: может быть несколько сложным для написания, требует некоторой программной гимнастики и не подходит для всех ситуаций.

В качестве побочного вопроса: существует ли безопасный способ подкласса с не виртуальными деструкторами (допустим, что я не хочу переопределять какие-либо методы, просто чтобы добавить новые)? Мое впечатление, что нет универсального и безопасного способа сделать это, если у вас нет возможности изменить код, определяющий не виртуальный класс.

ИЗМЕНИТЬ 2:

Как @doc указывает, объявления С++ 11 fancier using несколько снижают стоимость композиции.

4b9b3361

Ответ 1

Я предполагаю, что философия С++ не оплачивает функции, которые вы не используете. В зависимости от платформы указатель на виртуальную таблицу может быть дорогостоящей ценой, если вам не нужен виртуальный деструктор.

Ответ 2

Виртуальный деструктор полезен только для сценариев наследования. Контейнеры STL не предназначены для унаследованных (и не поддерживаемых сценариев). Следовательно, у них нет виртуальных деструкторов.

Ответ 3

Я думаю, что Страуструп косвенно ответил на этот вопрос в своей фантастической статье: Почему С++ - это не просто объектно-ориентированный язык программирования:

7 Заключительные замечания
Являются ли различные представленные выше объектно-ориентированный или нет? Какие? Используя какое определение объектно-ориентированный? В большинстве случаев я подумайте, что это неправильные вопросы. Важно то, какие идеи вы можете четко выразить, как легко вы можете комбинировать программное обеспечение от разных источников, а также эффективности и поддерживаемые результирующие программы находятся. Другими словами, как вы поддерживаете хорошие методы программирования и хорошие методы проектирования важны ярлыки и звуковые слова. Основные идея состоит в том, чтобы улучшить дизайн и программирование через абстракцию. Вы хотите скрыть подробности, вы хотите использовать любую общность в системе, и вы хотите сделать это доступным. Я хотел бы призвать вас не делать объективно ориентированным бессмысленным срок. Понятие "объектно ориентированное" слишком часто обесценивается

- by приравнивая его хорошим,

- путем приравнивания это с одним языком или

- by Принимая все как объектно.

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

STL был построен с использованием трех концептуальных инструментов. Общее программирование + Функциональный стиль + Абстракция данных == Стиль STL. Не странно, что ООП - это не лучший способ представить библиотеку данных и алгоритмов. Хотя ООП используется в других частях стандартной библиотеки, разработчик STL видел, что сочетание трех упомянутых методов лучше, чем ООП. Короче говоря, библиотека не была разработана с учетом ООП, а на С++, если вы ее не используете, она не входит в комплект с вашим кодом. Вы не платите за то, что не используете. Классы std::vector, std:: list,... представляют собой понятия не ООП в смысле Java/С#. Это просто абстрактные типы данных в наилучшей интерпретации.

Ответ 4

Почему не были контейнеры 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>, и он безопасен, поскольку единственная проблема с не виртуальным деструктором заключается в том, что он не будет вызывать производный, когда объект будет удален как экземпляр родительского класса.

... У меня также есть идея, что вы даже можете наследовать публично, если ваш класс не имеет деструктора. Ересь?

Ответ 5

Как уже указывалось, контейнеры STL не предназначены для наследования. Нет виртуальных методов, все члены данных являются частными, не защищеными getters/seters/helpers. И как вы обнаружили, нет виртуальных деструкторов.

Я бы предположил, что вы действительно должны использовать контейнеры через композицию, а не наследование реализации, как "has-a", а не "is-a".

Ответ 6

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

Ответ 7

Другим решением для подкласса из контейнеров 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 с виртуальными деструкторами.

Ответ 8

Если вам действительно нужен виртуальный деструктор, вы можете добавить его в класс, полученный из 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

Ответ 9

Ни один виртуальный деструктор не позволяет классу быть подклассами правильно.