unique_ptr 的保护析构函数

3

我想从第三方库中调用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与Parent类一起使用。但编译器抛出了错误。
/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;

有什么办法可以解决这个问题吗?由于这些类是第三方库的类,所以我无法对父类和子类进行修改。


2
您不允许通过父指针、unique_ptr、shared_ptr或原始指针进行删除操作。这是库作者强制执行的设计决策,而非疏忽或巧合。他们积极且故意地防止您这样做。无论这是否是一个好的设计选择,我无法判断,但它确实存在。 - n. m.
如果它确实是那个库,答案就在文档中!(https://pybind11.readthedocs.io/en/stable/advanced/classes.html#non-public-destructors) - Lightness Races in Orbit
@n.m.:我认为这个问题是真实代码的抽象版本或MCVE(即我假设,也许是错误的,使用了PyBind11)。我们是否认为第三方库真的有名为“Parent”和“Child”的类? - Lightness Races in Orbit
@LightnessRacesinOrbit,有“22==3.999999998”这样的bug,还有“22无法编译”的bug。 - n. m.
@n.m.: 缺少虚析构函数通常不会导致编译失败。 我说“通常”是因为这是未定义行为,所以我需要保守一点,但实际上,我从来没有见过这样的情况,也无法想象编译器如何能够在一般情况下诊断它。这种错误实际上很容易被忽视。 - Lightness Races in Orbit
显示剩余5条评论
2个回答

8
您可以将std::default_delete<Parent>设置为Parent的友元来解决此错误。您可能还需要将~Parent设置为virtual,以避免在通过Parent指针delete派生类时出现未定义行为。

例如:

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

然而,Parent 设计清楚地表明,你不应该通过 Parent 指针进行 delete 操作,这就是为什么析构函数是非公开的原因。阅读 Virtuality 以获取更多详细信息:

指南 #4:基类的析构函数应该是公共且虚拟的,或者是受保护且非虚拟的。


你可以引入另一个中间基类来解决这个问题:

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
可能还希望制作 - Lightness Races in Orbit
1
我无法修改父类,因为它是第三方库。 - alec.tu
3
在这种情况下,您不应该通过Parent指针删除内容,这就是为什么析构函数是非公共的原因。 - Maxim Egorushkin
1
std::default_delete 在 MSVC(2019)中是一个 struct,而不是一个 class,因此 friend class 会产生警告。我完全省略了 class,MSVC 没有问题(稍后将检查 GCC)。 - ddevienne
@ddevienne 谢谢您的评论,您是完全正确的,自C++11以来,friend不需要classstruct。已更新答案。 - Maxim Egorushkin

1
不幸的是,摆脱这个问题的真正方法是不要从Parent派生类,并且不要使用std::unique_ptr<Parent>管理Parent对象(或任何派生类)的生命周期。
换句话说,您需要重新设计您的类。
我这么说的原因是:
- 如果有人费心给Parent设置一个受保护的非虚析构函数,则意图很可能是为了避免将实际指向派生类实例的Parent *释放使用delete运算符。库设计者通常不会没有好的理由而这样做。 - 您的代码可以被强制编译(例如,通过使std::default_delete<Parent>成为Parent的友元)。但是,std::default_deleteunique_ptr用于释放管理对象。它使用delete运算符。如果被unique_ptr<Parent>管理的对象是从Parent派生的类型,则会产生未定义的行为。

简而言之,你正在绕过第三方库的设计者的意图,并且如果你强制编译代码,则会出现未定义的行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接