unique_ptr 对于非多态派生 class 与 C++20 销毁运算符 delete

unique_ptr for non-polymorphic derived class with C++20 destroying operator delete

C++20 销毁运算符 delete 的新特性,允许“挂钩”对析构函数的调用并“替换”它与我们自己的操作(例如,理论上,调用派生 class 的正确析构函数)。

是否可以使用 destroying operator delete,以允许 unique_ptr<A> 保存指向 class 的实际非多态派生 class 的指针=13=](即 A 中没有虚拟析构函数)而不需要 自定义删除器?

是的,这是可能的。事实上,它类似于 P0722 中针对 没有 vptrs 的动态调度提出的用例 .

在 C++20 之前,A unique_ptr<A> 持有指向 A 的派生 class 的指针需要:

  • 一个虚拟析构函数A——或者
  • 一个自定义删除器

C++20 规范添加了新的删除运算符 - destroying operator delete: calling delete on a static type which is different from the dynamic type to be deleted, does not fall in the undefined behavior case, if the selected deallocation function is a destroying operator delete, as detailed in [expr.delete](§7.6.2.9/3)(强调我的):

In a single-object delete expression, if the static type of the object to be deleted is different from its dynamic type and the selected deallocation function [...] is not a destroying operator delete, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. [...]

因此,为此目的使用 destroying operator delete 的选项是有效的。

例如,如果我们知道 A 永远不会被实例化并且 unique_ptr<A> 实际上总是持有类型 A_Proxy 的指针,我们可以执行 down castdestroying operator delete 中使用 static_cast 并调用适当的析构函数(同样可以在 custom 中完成deleter,现在可以免除)。

class A {
    friend struct A_Proxy;
    std::string s; // just an example of a member managed at A's level
    A(const char* str): s(str) {}
    ~A() {}
public:
    // Note: this is the destroying operator delete, as introduced in C++20
    void operator delete(A *p, std::destroying_delete_t);
    static std::unique_ptr<A> create(); // no need for a custom deleter
    void foo() const;
};

一个简单的派生classA_Proxy:

struct A_Proxy: A {
    A_Proxy(): A("A_Proxy") {}
    ~A_Proxy() { /* do anything that is required at the proxy level */ }
    void foo() const {
        std::cout << "A_Proxy::foo()" << std::endl;         
    }
};

随着 A 的实现:

void A::operator delete(A *p, std::destroying_delete_t) {
    // in this example we know for sure p is of type A_Proxy*
    ::delete static_cast<A_Proxy*>(p);
    // ^ call the global ::delete to avoid recursion
}

std::unique_ptr<A> A::create() {
    return std::make_unique<A_Proxy>(); // without the need for a custom deleter
}

void A::foo() const {
    static_cast<const A_Proxy*>(this)->foo();
}

主要:

int main () {
    auto a = A::create();
    auto b = a.release();
    a = std::unique_ptr<A>(b);
    a->foo();
}

The code above - with a destroying operator delete.


但是要注意这里没有真正的魔法。使用自定义删除器会产生非常相似的代码.另请注意,unique_ptr 的大多数(如果不是全部)实现都将具有 stateless custom deleter 的裸指针大小,可用于此目的。

The same code as above - but with a custom deleter。 <= 请注意,此实现中带有 自定义删除器 unique_ptr 的大小与裸指针相同。


上述技术也适用于存在多个可能的转换的情况,例如,通过在基础 class 中使用适当的 类型标志 :

void A::operator delete(A *p, std::destroying_delete_t) {
    if(p->type == "A") {
        ::delete p;
    }
    else if(p->type == "B") {
        ::delete static_cast<B*>(p);
    }
    else if(p->type == "C") {
        ::delete static_cast<C*>(p);
    }
    else {
        throw "unsupported type";
    }
}

再次,这两种方法 - destroying operator delete approach and the custom deleter approach 将产生非常相似的代码和 unique_ptr 的裸指针大小(在 销毁运算符删除 方法unique_ptr 大小确保 为裸指针大小,在自定义删除器 方法 它很可能是 ,如果你正确地实现删除器,并且取决于 unique_ptr) 的实际实现。

我会通过提供自己的 make_unique 版本来解决这个问题,而不是尝试使用 C++20 中的一些新功能。

template<typename Base, typename T, typename ...Args>
auto base_make_unique(Args&&...args) ->
        typename std::enable_if<!std::has_virtual_destructor<Base>::value,
                                std::unique_ptr<Base, void(*)(Base*)>
                            >::type
{
    return std::unique_ptr<Base, void(*)(Base*)>{
        new T{std::forward<Args>(args)...},
        &has_virtual_destructor_deleter<Base, T>
    };
}

template<typename Base, typename T, typename ...Args>
auto base_make_unique(Args&&...args) ->
        typename std::enable_if<std::has_virtual_destructor<Base>::value,
                                std::unique_ptr<Base>
                            >::type
{
    return std::unique_ptr<Base>{new T{std::forward<Args>(args)...}};
}

https://godbolt.org/z/E7Tasv