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 cast 在 destroying 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)...}};
}
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 cast 在 destroying 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)...}};
}