Защищенный деструктор с unique_ptr

Я пытаюсь вызвать API из сторонней библиотеки.

Возникает проблема, когда я хочу использовать unique_ptr с классом, имеющим защищенный деструктор.

Вот пример,

#include <memory>
#include <iostream>

using namespace std;

class Parent {  
  public:  
    Parent () //Constructor
    {
        cout << "\n Parent constructor called\n" << endl;
    }
  protected:
    ~ Parent() //Dtor
    {
        cout << "\n Parent destructor called\n" << endl;
    }
};

class Child : public Parent 
{
  public:
    Child () //Ctor
    {
        cout << "\nChild constructor called\n" << endl;
    }
    ~Child() //dtor
    {
        cout << "\nChild destructor called\n" << endl;
    }
};

Parent* get() {
  return new Child();
}

int main(int argc, char const* argv[])
{
  Parent * p1 = get(); // this is ok
  std::unique_ptr<Parent> p2(get()); // this is not ok
  return 0;
}

Я пытаюсь использовать unique_ptr с родительским классом. Но компилятор выдал ошибки

/usr/include/c++/5/bits/unique_ptr.h: In instantiation 
of ‘void std::default_delete<_Tp>::operator()(_Tp*) const 
[with _Tp = Parent]’:
/usr/include/c++/5/bits/unique_ptr.h:236:17:   required 
from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp 
= Parent; _Dp = std::default_delete<Parent>]’
main.cpp:38:35:   required from here
main.cpp:12:5: error: ‘Parent::~Parent()’ is protected
 ~ Parent() //Dtor
 ^
In file included from /usr/include/c++/5/memory:81:0,
             from main.cpp:2:
/usr/include/c++/5/bits/unique_ptr.h:76:2: error: within 
this context
  delete __ptr;

Любая идея о том, как избавиться от этой проблемы? Я не могу взломать родительский и дочерний классы, так как они являются классами сторонней библиотеки.

Вам не разрешено удалять через родительский указатель, unique_ptr или shared_ptr или необработанный указатель. Это вынужденное конструкторское решение авторов библиотеки. Не оплошность, не совпадение. Они активно и намеренно мешают вам это делать. Я не могу сказать, хороший ли это дизайнерский выбор, но он есть.

n. 1.8e9-where's-my-share m. 30.05.2019 15:17

@н.м. Справедливости ради, из этого вопроса мы не можем узнать, было ли это преднамеренно или нет. Это может быть просто ужасный баг! Как ни странно, в предыдущий вопрос ОП (какой кажется примерно та же проблема) базовым деструктором является virtual, а авторский дизайн библиотеки, по-видимому, не игнорирует ни одно из этих соображений.. Предположим, что с прошлой недели ОП мог перейти в какую-то другую библиотеку....

Lightness Races in Orbit 30.05.2019 15:24

(А если это является то библиотека, то ответ прямо в документах!)

Lightness Races in Orbit 30.05.2019 15:27

@LightnessRacesinOrbit да, это может быть ошибкой, если OP является единственным пользователем библиотеки. В противном случае это было бы исправлено. Я не уверен, что вы подразумеваете под библиотекой это. Этот код не является частью PyBind11.

n. 1.8e9-where's-my-share m. 30.05.2019 15:33

@n.m.: я интерпретировал этот вопрос как абстрактную/MCVE-версию реального кода (который, в свою очередь, я предполагаю, возможно, ошибочно, использует PyBind11). Думаем ли мы, что в сторонней библиотеке действительно есть классы с именами Parent и Child?

Lightness Races in Orbit 30.05.2019 15:38

@n.m.: «да, это может быть ошибкой, если OP является единственным пользователем библиотеки. В противном случае это будет исправлено» По этой логике никакое программное обеспечение/библиотека, используемая двумя или более людьми во всем мире, не будет содержать ошибок!

Lightness Races in Orbit 30.05.2019 15:39

@LightnessRacesinOrbit есть ошибки «2 * 2 == 3,999999998», а затем есть ошибки «2 * 2 не компилируются».

n. 1.8e9-where's-my-share m. 30.05.2019 15:46

@n.m.: Отсутствие виртуального деструктора обычно не приводит к сбою компиляции. Я говорю «обычно», потому что это UB, поэтому мне нужно хеджировать свои ставки, но на самом деле я был свидетелем такого никогда, и я не могу представить, как компилятор сможет это диагностировать в целом. Такую ошибку на самом деле довольно легко пропустить.

Lightness Races in Orbit 30.05.2019 15:48

@LightnessRacesinOrbit Отсутствие виртуального деструктора обычно не приводит к сбою компиляции, в отличие от защищенного, и это именно тот сбой, который мы здесь наблюдаем.

n. 1.8e9-where's-my-share m. 30.05.2019 15:51

@н.м. Ха, хорошо, я дам тебе это. Хотя (снова делая дикое предположение о реальном коде) библиотека действительно разрешает это и дает программисту инструменты для этого (см. ссылки)

Lightness Races in Orbit 30.05.2019 15:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
10
946
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете сделать std::default_delete<Parent> другом Parent, чтобы исправить эту ошибку. И вы также можете сделать ~Parentvirtual, чтобы избежать неопределенного поведения при delete передаче производного класса через Parent указатель.

Например.:

class Parent { 
    friend std::default_delete<Parent>;
    // ...
protected:
    virtual ~Parent();
    // ...

Тем не менее, дизайн Parent ясно дает понять, что вы не должны delete через Parent указатель, поэтому деструктор не является общедоступным. Прочтите Виртуальность для более подробной информации:

Guideline #4: A base class destructor should be either public and virtual, or protected and nonvirtual.


Вы можете ввести еще один промежуточный базовый класс для решения проблемы:

class Parent { // Comes from a 3rd-party library header.
protected:
    ~Parent();
};

struct MyParent : Parent {  // The intermediate base class.
    virtual ~MyParent();
};

class Derived : public MyParent {};

std::unique_ptr<MyParent> createDerived() {
    return std::unique_ptr<MyParent>(new Derived);
}

int main() {
    auto p = createDerived();
}

Ха, здорово! Просто и элегантно :)

Lightness Races in Orbit 30.05.2019 13:46
"может также сделать" => придется
Lightness Races in Orbit 30.05.2019 13:50

Я не могу изменить родительский класс, так как это сторонняя библиотека.

alec.tu 30.05.2019 13:52

@alec.tu В этом случае вы не должны удалять через указатель Parent, поэтому деструктор не является общедоступным.

Maxim Egorushkin 30.05.2019 13:53
std::default_delete — это struct в MSVC (2019), а не class, поэтому friend class выдает предупреждение. Я просто полностью опустил class, и MSVC был в порядке (позже проверю GCC).
ddevienne 16.11.2021 14:02

@ddevienne Спасибо за ваш комментарий, вы совершенно правы, так как C++11 friend не требует class или struct. Обновил ответ.

Maxim Egorushkin 16.11.2021 17:06

К сожалению, реальный способ избавиться от этой проблемы — не наследовать классы от Parent и не управлять временем жизни Parent объектов (или любых производных классов) с помощью std::unique_ptr<Parent>.

Другими словами, вам нужно перепроектировать свои классы.

Причины, по которым я это говорю,

  • Если кто-то потрудился предоставить Parent защищенный невиртуальный деструктор, скорее всего, его намерение состоит в том, чтобы избежать Parent *, который фактически указывает на экземпляр производного класса и освобождается с помощью оператора delete. Дизайнер библиотеки не будет (обычно) делать это без веской причины.
  • Ваш код можно принудительно скомпилировать как есть (например, сделав std::default_delete<Parent> другом Parent). Однако std::default_delete используется unique_ptr для освобождения управляемого объекта. И он использует оператор delete. Это дает неопределенное поведение, если объект, которым управляет unique_ptr<Parent>, имеет тип, производный от Parent.

Короче говоря, вы работаете над намерением (кто бы ни разработал) вашу стороннюю библиотеку, и, если вы все равно принудительно скомпилируете код, ваша награда будет неопределенным поведением.

Другие вопросы по теме