Почему в контейнерах STL нет виртуальных деструкторов? - PullRequest
31 голосов
/ 30 октября 2009

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

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ 2:

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

Ответы [ 9 ]

30 голосов
/ 30 октября 2009

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

17 голосов
/ 30 октября 2009

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

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

- по приравнивая это к добру,

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

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

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

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

12 голосов
/ 30 октября 2009

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

7 голосов
/ 14 декабря 2012

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

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

1 голос
/ 30 октября 2009

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

1 голос
/ 30 октября 2009

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

Я бы посоветовал вам действительно использовать контейнеры с помощью компоновки, а не наследования реализации, способом "имеет", а не "есть".

0 голосов
/ 13 января 2016

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

Пример:

#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
0 голосов
/ 24 ноября 2015

Еще одно решение для создания подклассов из контейнеров STL - это решение, предложенное Бо Цянем с использованием умных указателей.

Advanced C ++: виртуальный деструктор и интеллектуальный деструктор

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;
}

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

0 голосов
/ 30 октября 2009

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

...