Я пытаюсь вызвать 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;
Любая идея о том, как избавиться от этой проблемы? Я не могу взломать родительский и дочерний классы, так как они являются классами сторонней библиотеки.
@н.м. Справедливости ради, из этого вопроса мы не можем узнать, было ли это преднамеренно или нет. Это может быть просто ужасный баг! Как ни странно, в предыдущий вопрос ОП (какой кажется примерно та же проблема) базовым деструктором является virtual
, а авторский дизайн библиотеки, по-видимому, не игнорирует ни одно из этих соображений.. Предположим, что с прошлой недели ОП мог перейти в какую-то другую библиотеку....
(А если это является то библиотека, то ответ прямо в документах!)
@LightnessRacesinOrbit да, это может быть ошибкой, если OP является единственным пользователем библиотеки. В противном случае это было бы исправлено. Я не уверен, что вы подразумеваете под библиотекой это. Этот код не является частью PyBind11.
@n.m.: я интерпретировал этот вопрос как абстрактную/MCVE-версию реального кода (который, в свою очередь, я предполагаю, возможно, ошибочно, использует PyBind11). Думаем ли мы, что в сторонней библиотеке действительно есть классы с именами Parent
и Child
?
@n.m.: «да, это может быть ошибкой, если OP является единственным пользователем библиотеки. В противном случае это будет исправлено» По этой логике никакое программное обеспечение/библиотека, используемая двумя или более людьми во всем мире, не будет содержать ошибок!
@LightnessRacesinOrbit есть ошибки «2 * 2 == 3,999999998», а затем есть ошибки «2 * 2 не компилируются».
@n.m.: Отсутствие виртуального деструктора обычно не приводит к сбою компиляции. Я говорю «обычно», потому что это UB, поэтому мне нужно хеджировать свои ставки, но на самом деле я был свидетелем такого никогда, и я не могу представить, как компилятор сможет это диагностировать в целом. Такую ошибку на самом деле довольно легко пропустить.
@LightnessRacesinOrbit Отсутствие виртуального деструктора обычно не приводит к сбою компиляции, в отличие от защищенного, и это именно тот сбой, который мы здесь наблюдаем.
@н.м. Ха, хорошо, я дам тебе это. Хотя (снова делая дикое предположение о реальном коде) библиотека действительно разрешает это и дает программисту инструменты для этого (см. ссылки)
Вы можете сделать std::default_delete<Parent>
другом Parent
, чтобы исправить эту ошибку. И вы также можете сделать ~Parent
virtual
, чтобы избежать неопределенного поведения при 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();
}
Ха, здорово! Просто и элегантно :)
Я не могу изменить родительский класс, так как это сторонняя библиотека.
@alec.tu В этом случае вы не должны удалять через указатель Parent
, поэтому деструктор не является общедоступным.
std::default_delete
— это struct
в MSVC (2019), а не class
, поэтому friend class
выдает предупреждение. Я просто полностью опустил class
, и MSVC был в порядке (позже проверю GCC).
@ddevienne Спасибо за ваш комментарий, вы совершенно правы, так как C++11 friend
не требует class
или struct
. Обновил ответ.
К сожалению, реальный способ избавиться от этой проблемы — не наследовать классы от Parent
и не управлять временем жизни Parent
объектов (или любых производных классов) с помощью std::unique_ptr<Parent>
.
Другими словами, вам нужно перепроектировать свои классы.
Причины, по которым я это говорю,
Parent
защищенный невиртуальный деструктор, скорее всего, его намерение состоит в том, чтобы избежать Parent *
, который фактически указывает на экземпляр производного класса и освобождается с помощью оператора delete
. Дизайнер библиотеки не будет (обычно) делать это без веской причины.std::default_delete<Parent>
другом Parent
). Однако std::default_delete
используется unique_ptr
для освобождения управляемого объекта. И он использует оператор delete
. Это дает неопределенное поведение, если объект, которым управляет unique_ptr<Parent>
, имеет тип, производный от Parent
.Короче говоря, вы работаете над намерением (кто бы ни разработал) вашу стороннюю библиотеку, и, если вы все равно принудительно скомпилируете код, ваша награда будет неопределенным поведением.
Вам не разрешено удалять через родительский указатель, unique_ptr или shared_ptr или необработанный указатель. Это вынужденное конструкторское решение авторов библиотеки. Не оплошность, не совпадение. Они активно и намеренно мешают вам это делать. Я не могу сказать, хороший ли это дизайнерский выбор, но он есть.