简体   繁体   English

用于非多态派生类的 unique_ptr 与 C++20 销毁运算符删除

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

C++20 new feature of destroying operator delete , allows to "hook" the call to the destructor and "replace" it with our own operation (eg theoretically, calling the proper destructor of a derived class). C++20销毁运算符 delete 的新特性,允许“挂钩”对析构函数的调用并用我们自己的操作“替换”它(例如,理论上,调用派生类的适当析构函数)。

Is it possible to use destroying operator delete , to allow a unique_ptr<A> to hold a pointer to an actual non-polymorphic derived class of A (ie no virtual destructor in A ) without a need for a custom deleter ?是否有可能使用破坏delete操作符,允许unique_ptr<A>的指针坚持一个实际的非多态性派生类中的A (即在没有虚拟析构函数A ),而不需要一个定制删除

Yes, it is possible.对的,这是可能的。 In fact it resembles a use case raised in P0722 for Dynamic dispatch without vptrs .事实上,它类似于P0722 中提出的一个用例,用于没有 vptrs 的动态调度

Before C++20 A unique_ptr<A> holding a pointer to a derived class of A required either:在 C++20 之前,一个unique_ptr<A>持有一个指向A的派生类的指针,需要:

  • a virtual destructor in A -- OR A虚拟析构函数- 或
  • a custom deleter自定义删除器

The C++20 specification added new delete operator - 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) (emphasis mine): C++20规范增加了新的删除操作符—— 破坏操作符delete :在与要删除的动态类型不同的静态类型上调用delete,不属于未定义行为的情况,如果选择的释放函数是破坏操作符删除,详见[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.在单对象删除表达式中,如果要删除的对象的静态类型与其动态类型不同,并且选择的释放函数 [...] 不是销毁操作符 delete ,则静态类型应为要删除的对象的动态类型和静态类型应具有虚拟析构函数或行为未定义。 [...] [...]

So, the option of using destroying operator delete for this purpose is valid.因此,为此目的使用销毁运算符 delete的选项是有效的。

For example, if we know that A would never be instantiated and that unique_ptr<A> would actually always hold a pointer of type A_Proxy , we can perform a down cast in the destroying operator delete by using static_cast and call the proper destructor (the same could have been done in a custom deleter , which can be waived now).例如,如果我们知道A永远不会被实例化并且unique_ptr<A>实际上总是持有一个类型为A_Proxy的指针,我们可以使用static_cast销毁操作符 delete 中执行向下转换并调用适当的析构函数(相同可以在自定义删除器中完成,现在可以放弃)。

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;
};

A simple derived class A_Proxy :一个简单的派生类A_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;         
    }
};

With the implementations of A :随着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();
}

Main:主要的:

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 .上面的代码 - 带有销毁操作符 delete


It is to be noted however that there is no real magic here.然而需要注意的是,这里没有真正的魔法。 Using a custom deleter would result with quite a similar code.使用自定义删除器会产生非常相似的代码。 Note also that most if not all implementations of unique_ptr would have a bare pointer size for a stateless custom deleter , which can be used for this purpose.另请注意,如果不是所有的unique_ptr实现,大多数(如果不是所有)都将具有用于无状态自定义删除器的裸指针大小,可用于此目的。

The same code as above - but with a custom deleter .与上面相同的代码 - 但带有自定义删除器 <= Note that the size of the unique_ptr with the custom deleter in this implementation is the same as a bare pointer. <= 请注意,在此实现中带有自定义删除器unique_ptr的大小与裸指针相同。


Above technique can be relevant also in cases where there is more than a single possible cast, by using a proper type flag in the base class, for example:通过在基类中使用适当的类型标志,上述技术也适用于存在多个可能的强制转换的情况,例如:

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";
    }
}

And again , both approaches - the destroying operator delete approach and the custom deleter approach would result with quite a similar code and a bare pointer size for the unique_ptr (in the destroying operator delete approach the unique_ptr size is ensured to be of bare pointer size, in the custom deleter approach it would most probably be , if you implement the deleter properly, and depending on the actual implementation of unique_ptr ).同样,这两种方法 -销毁运算符删除方法自定义删除器方法将导致非常相似的代码和unique_ptr的裸指针大小(在销毁运算符删除方法中, unique_ptr大小确保为裸指针大小,在自定义删除器方法,如果您正确实现删除器,并且取决于unique_ptr的实际实现,它很可能是)。

I would approach this problem by providing own version of make_unique instead trying use some new feature from C++20.我会通过提供自己的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 https://godbolt.org/z/E7Tasv

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM