问题描述
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 approach和custom 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)...}};
}