对于使用C ++ 20破坏运算符delete的非多态派生类,则为unique_ptr

问题描述

销毁运算符delete

C ++ 20新功能,可以“钩住”对析构函数调用,并用我们自己的操作“替代”(例如,理论上,调用派生类的适当析构函数

是否可以使用 destroying运算符delete 来允许unique_ptr<A>持有指向A的实际非多态派生类的指针(即没有虚拟析构函数A中的内容,而无需自定义删除

解决方法

是的,有可能。实际上,它类似于在P0722中提出的用于没有vptrs的动态调度的用例。

在C ++ 20之前,unique_ptr<A>持有指向A派生类的指针的任何一个:

  • A中的虚拟析构函数 –或
  • 一个自定义删除器

C ++ 20规范添加了新的删除操作符-destroying operator delete:在与要删除的动态类型不同的静态类型上调用delete,如果选择的取消分配,则不属于未定义行为的情况函数是销毁操作符删除,如[expr.delete](§7.6.2.9/3)(强调我的内容)所述:

在单对象删除表达式中,如果要删除的对象的静态类型不同于其动态类型,并且所选的释放函数[...]不是破坏运算符delete ,静态类型应为要删除的对象的动态类型的基类,并且静态类型应具有虚拟析构函数或行为未定义。 [...]

因此,为此目的使用销毁运算符delete 的选项是有效的。

例如,如果我们知道永远不会实例化A并且unique_ptr<A>实际上总是持有类型A_Proxy的指针,我们可以执行 down cast ,使用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_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的大小与裸指针相同。


通过在基类中使用适当的 type标志,例如,在存在多个可能的强制转换的情况下,

上述技术也可能相关:

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 approachcustom deleter approach都将得到非常相似的代码和unique_ptr的裸指针大小(在销毁操作符的删除方法是{strong>确保的大小是裸指针大小,在自定义删除器方法中是如果您正确地实现了删除程序,并且取决于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 ...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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...