【问题标题】:Why unique-ptr doesn't check base class to virtual destructible?为什么 unique-ptr 不检查基类是否可虚拟破坏?
【发布时间】:2023-03-08 11:33:01
【问题描述】:

考虑这个例子:

#include <cstdio>
#include <memory>

struct base
{
    base( int i ): i(i)    {    printf("base ctor\n"); }
    ~base() {     printf("base non-virtual dtor\n"); } // non-virtual
    int i;
};

struct derived : public base
{
    char* s;
    derived(int i): base(i), s(new char[i] )
    {
        printf("derived ctor\n");
    }
    ~derived()
    {
        printf("derived dtor\n");
        delete [] s;
    }
};

int main()
{
    printf("Success\n");

    //raw pointer
    printf("RAW-POINTER\n");
    {
        base* b = new derived(2);
         // ......
        delete b; //here memory leak, but it's old- and error-prone code.
    }
    printf("---END-RAW-POINTER--\n");

    //unique-ptr
    printf("UNIQUE_PTR\n");
    {
       // I would that, this doesn't compile, because base- has not virtual destructor.
        std::unique_ptr<base> bu( new derived(3) ); // here still memory leak !!!!
    }
    printf("--END-UNIQUE_PTR--\n");


    return 0;
}

代码std::unique_ptr&lt;base&gt; bu( new derived(3) ); 易于使用std::has_virtual_destructor 类型特征禁止。 Live code

那么为什么要编译上面的代码呢?这是标准允许的吗?

编辑:有趣,但 std::shared_ptr 有效,即基础和派生 dtor 都会调用:

    printf("SHARED_PTR\n");
    {
        std::shared_ptr<base> b(new derived(3));
    }
    printf("--END-SHARED_PTR--\n");

Output:
SHARED_PTR
base ctor
derived ctor
derived dtor
base non-virtual dtor
--END-SHARED_PTR--

为什么 std::shared_ptr 可以调用派生类 dtor,而 std::unique_ptr 不能???

EDIT2:简单我需要类似的东西:

template< typename T, typename D = default_deleter<T> >
class unique_ptr{
  .............

  template< typename U >
  unique_ptr( U* u ) if ( U != T && T - is class && T is base of U, and D - is default_deleter, and T - has not virtual destructor ) then = delete this ctor.

};

【问题讨论】:

  • @Brian Bi:并非所有都使用自定义删除器,在许多情况下用户使用 default_deleter 进行限制。
  • std::shared_ptr 将工作。它将调用基类和派生类的析构函数:ideone.com/Nc542V
  • 如果你使用public继承,你应该定义析构函数为virtual。 (stackoverflow.com/questions/270917/…)
  • 删除p 的静态类型与动态类型不同的指针p 是未定义行为,但不是禁止的(= 不是格式错误的)。此外,您必须检查 unique_ptrdtor,因为您可以提供不完整的类型,只要它们在 unique_ptr 的 dtor 实例化时是完整的(或他们的 dtor 是微不足道的)。

标签: c++ c++11 polymorphism unique-ptr


【解决方案1】:

unique_ptrshared_ptr 之间的区别在于标准的语言,关于它们的析构函数(和构造函数)。适用于您的示例的两个智能指针的删除器的语言相似但略有不同:

[20.7.1.2.2] unique_ptr 析构函数 ...如果 get() == nullptr 没有效果。否则 get_deleter()(get())。 [20.7.2.2.2] shared_ptr 析构函数 ... 如果 *this 拥有一个对象 p 和一个删除器 d,则调用 d(p)。

您可以看到,在这两种情况下,标准都要求调用删除器,但在决定删除器的方式上有所不同,unique_ptr 删除了它通过get() 获得的 指针 ,而shared_ptr 删除对象。这种区别很重要。看看这两个类的构造函数也有什么不同:

shared_ptr 定义如下:

template <class  T> 
class  shared_ptr  {
...
    template<class  Y>  explicit  shared_ptr(Y*  p);

虽然unique_ptr 显式单参数构造函数是,

template <class  T, class D = default_delete<T>>
class  unique_ptr  {
...
    explicit  unique_ptr(pointer  p)  noexcept;
...

注意 unique_ptr 只是获取该类型的默认删除,在您的情况下将是普通的 delete,并存储指针。但是,shared_ptr&lt;T&gt; 构造函数不是在 T (!) 上模板化的,它是在构造它的对象 Y 上模板化的。因此,在您的场景中,

std::shared_ptr<base> b(new derived(3));

shared_ptr 将使用T=baseY=derived 构造,允许显式销毁派生对象,并且在您的示例中不会泄漏内存。


虽然您无法更改标准,但您可以做的是从项目中的unique_ptr 继承,或者提供您自己的包装器来强制执行所需的行为。例如,

namespace {
    template <class  T>
    struct checked_delete : public std::default_delete<T> {
        static_assert(std::has_virtual_destructor<T>::value, "");
    };    

    template <class T, class D = checked_delete<T>, class U>  
    std::unique_ptr<T, D>
    make_unique_ptr(U* p) { return std::unique_ptr<T, D>(p, D()); }    
}

// now this won't compile, because checked_delete<base> will not compile:
auto bu = make_unique_ptr<base>(new derived(3));

【讨论】:

  • 您的最后一个示例是非常好的解决方案,但我只需要在从派生类构造虚拟 dtor 时检查它,而不是每一种情况。但是,非常感谢。现在,我知道它是如何实现的了。
【解决方案2】:

并非所有的 unique_pointers 都以多态方式使用:

std::unique_ptr<int> p(new int(42));

这将无法按照您建议的限制进行编译。与类相同:

std::unique_ptr<YourClassHere> p(new YourClassHere);

【讨论】:

  • int - 不是类类型。见 is_class:en.cppreference.com/w/cpp/types/is_class
  • 您的第二个示例,模板参数 unique_ptr 和参数构造函数 - 是相同的类。如果它们是不同的类,会发生什么?在这种情况下,如果基类没有虚拟析构函数,派生类析构函数将不会被调用。
  • 这与使用原始指针得到的行为完全相同。
  • 原始指针容易出错并且大家都已经知道了,但是 std::unique_ptr - 更了解 ideome,它可能会在编译时的一些错误中检查。我问为什么 std::unique_ptr 从派生类构造时没有检查虚拟 dtor 基类,100% 可以检查。
猜你喜欢
  • 1970-01-01
  • 2015-07-02
  • 1970-01-01
  • 2013-12-01
  • 1970-01-01
  • 2014-03-15
  • 2020-10-21
  • 1970-01-01
  • 2011-03-16
相关资源
最近更新 更多