Локальные переменные v1
и v2
имеют автоматическое c время хранения и будут автоматически уничтожены, когда они go выйдут из области видимости. std::vector
здесь не имеет значения: внутри vector::~vector()
компилятор сгенерирует код для деструкторов элементов. Даже если вектор всегда пуст (это свойство времени выполнения!), Этот код все равно нужно сгенерировать. Итак, давайте упростим код:
std::unique_ptr<Base> v1;
std::shared_ptr<Base> v2;
Когда v1
выходит из области видимости, его нужно уничтожить. Деструктор, сгенерированный компилятором, сводится к (*):
~unique_ptr() {
delete ptr;
}
Чтобы сгенерировать код для delete ptr
, компилятору необходим доступный деструктор. Он защищен, поэтому компиляция не удалась.
Теперь давайте посмотрим на v2
. Компилятор также должен генерировать деструктор. Но shared_ptr
имеет удаленное по типу средство удаления для управляемого объекта. Это означает, что деструктор управляемого объекта будет вызываться косвенно - через виртуальную функцию:
struct shared_ptr_deleter_base {
virtual void destroy() = 0;
virtual ~shared_ptr_deleter_base() = default;
};
~shared_ptr() {
// member shared_ptr::deleter has type shared_ptr_deleter_base*
if (deleter)
deleter->destroy();
}
Чтобы сгенерировать код для deleter->destroy()
, вам вообще не нужен доступ к Base::~Base()
. Конструктор по умолчанию shared_ptr
просто устанавливает deleter
в нулевой указатель:
shared_ptr() {
deleter = nullptr;
}
Вот почему std::shared_ptr<Base> v2;
компилируется: не только Base::~Base()
не вызывается во время выполнения, ни один вызов не когда-либо созданный компилятором во время компиляции.
Давайте рассмотрим эту строку:
std::shared_ptr<Base> v2(new Base());
Теперь вызывается следующий конструктор (обратите внимание, что это шаблон с отдельным параметром U
может отличаться от T
в shared_ptr<T>
):
template<class U>
shared_ptr(U* ptr) {
deleter = new shared_ptr_deleter<U>(ptr);
}
Здесь shared_ptr_deleter
- это конкретный класс, производный от shared_ptr_deleter_base
:
template<class T>
struct shared_ptr_deleter : shared_ptr_deleter_base {
T* ptr;
shared_ptr_deleter(T* p) : ptr(p) {}
virtual void destroy() {
delete ptr;
}
};
Для генерации кода для конструктора, принимающего new Base()
, компилятор должен сгенерировать код для shared_ptr_deleter<Base>::destroy()
. Теперь это терпит неудачу, потому что Base::~Base()
недоступен.
(*) Я представляю только упрощенные определения, просто чтобы продемонстрировать основные идеи c, не вдаваясь во все детали, которые не имеют отношения к пониманию рассматриваемой проблемы.