删除使用 C++17 pmr 内存资源分配的多态对象
Deleting polymorphic objects allocated with C++17 pmr memory resource
我想构建一棵由 Node
类型的多态对象组成的树,这些对象是
使用自定义 PMR 分配器分配。
到目前为止,一切正常,但我不知道如何正确删除使用非标准分配器分配的多态对象?我只是想出了一个解决方案来声明
一个静态对象持有对 std::pmr::memory_resource
.. 的引用,但这很讨厌。
是否有任何 "right" 删除自定义分配的多态对象的方法?
这是一个自包含的例子:
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <functional>
#include <memory_resource>
struct Node {
// NOTE: this is actually not necessary..
using allocator_type = std::pmr::polymorphic_allocator<Node>;
void operator delete(void *ptr, std::size_t sz) noexcept {
Node::deleter(ptr, sz);
}
// don't bother with getters/setters so far..
std::pmr::string name;
template <class TNode >
static TNode *create(std::string_view name, std::pmr::memory_resource *res) {
std::pmr::polymorphic_allocator<TNode> alloc(res);
auto ptr = alloc.allocate(1);
::new (ptr) TNode(alloc);
ptr->name = name;
return ptr;
}
virtual ~Node() {
std::cerr << "Destructing node: " << name << std::endl;
}
// NASTY: pointer to memory resource to delete polymorphic objects..
static std::pmr::memory_resource *s_deleterResource;
protected:
Node(const allocator_type& alloc) : name(alloc) {}
static void deleter(void *ptr, std::size_t sz) noexcept {
if (s_deleterResource != nullptr) {
std::cerr << "Deleting mem: " << ptr << " of size: " << sz << " using PMR resource\n";
std::pmr::polymorphic_allocator< char >(s_deleterResource)
.deallocate((char *)ptr, sz);
}
else {
std::cerr << "Deleting mem: " << ptr << " of size: " << sz << " using default\n";
::operator delete(ptr, sz);
}
}
};
decltype (Node::s_deleterResource) Node::s_deleterResource = nullptr;
struct CompoundNode : Node {
friend struct Node;
using allocator_type = std::pmr::polymorphic_allocator<CompoundNode>;
void operator delete(void *ptr, std::size_t sz) noexcept {
Node::deleter(ptr, sz);
}
void addChild(Node *child) {
m_children.push_back(child);
}
~CompoundNode() override {
for(auto child : m_children) {
delete child;
}
}
protected:
explicit CompoundNode(const allocator_type& alloc) :
Node(alloc), m_children(alloc)
{ }
std::pmr::vector< Node * > m_children;
};
struct LeafNode : Node {
friend struct Node;
using allocator_type = std::pmr::polymorphic_allocator<LeafNode>;
void operator delete(void *ptr, std::size_t sz) noexcept {
Node::deleter(ptr, sz);
}
~LeafNode() override {
// NOTE: this is probably won't work since the object m_payload is not yet destroyed
//allocator_type alloc(m_payload.get_allocator());
//alloc.deallocate(this, 1);
}
protected:
explicit LeafNode(const allocator_type& alloc) :
Node(alloc), m_payload(77, alloc) { }
std::pmr::vector< uint8_t > m_payload;
};
// adding verbosity to the existing memory resource
struct VerboseMemResource : public std::pmr::memory_resource {
VerboseMemResource(const char *name, std::pmr::memory_resource *base)
: m_name(name), m_resource(base) { }
private:
void *do_allocate(std::size_t bytes, std::size_t alignment) override {
auto ptr = m_resource->allocate(bytes, alignment);
std::cerr << "Allocated: " << bytes << " bytes with '" << m_name << "': " << ptr << std::endl;
return ptr;
}
void do_deallocate(void *ptr, std::size_t bytes, std::size_t alignment) override {
std::cerr << "Deallocating " << bytes << " bytes with '" << m_name << "': " << ptr << std::endl;
return m_resource->deallocate(ptr, bytes, alignment);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return m_resource->is_equal(other);
}
std::string m_name;
std::pmr::memory_resource *m_resource;
};
int main() try
{
std::array< uint8_t, 1000 > buf;
std::pmr::monotonic_buffer_resource bufferRes(buf.data(), buf.size(),
std::pmr::null_memory_resource());
VerboseMemResource res("buffered resource", &bufferRes);
auto root = Node::create<CompoundNode>("root", &res);
root->addChild(Node::create<LeafNode>("child1", &res));
root->addChild(Node::create<LeafNode>("child2", &res));
// set the pointer to our memory resource for deletion:
Node::s_deleterResource = &res;
std::cerr << "Beginning tree deletetion..\n";
delete root;
Node::s_deleterResource = nullptr;
return 0;
}
catch(std::exception& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
return 1;
}
这是输出:
Allocated: 80 bytes with 'buffered resource': 0000006D21FDF740
Allocated: 80 bytes with 'buffered resource': 0000006D21FDF790
Allocated: 77 bytes with 'buffered resource': 0000006D21FDF7E0
Allocated: 8 bytes with 'buffered resource': 0000006D21FDF830
Allocated: 80 bytes with 'buffered resource': 0000006D21FDF838
Allocated: 77 bytes with 'buffered resource': 0000006D21FDF888
Allocated: 16 bytes with 'buffered resource': 0000006D21FDF8D8
Deallocating 8 bytes with 'buffered resource': 0000006D21FDF830
Beginning tree deletetion..
Deallocating 77 bytes with 'buffered resource': 0000006D21FDF7E0
Destructing node: child1
Deleting mem: 0000006D21FDF790 of size: 80 using PMR resource
Deallocating 80 bytes with 'buffered resource': 0000006D21FDF790
Deallocating 77 bytes with 'buffered resource': 0000006D21FDF888
Destructing node: child2
Deleting mem: 0000006D21FDF838 of size: 80 using PMR resource
Deallocating 80 bytes with 'buffered resource': 0000006D21FDF838
Deallocating 16 bytes with 'buffered resource': 0000006D21FDF8D8
Destructing node: root
Deleting mem: 0000006D21FDF740 of size: 80 using PMR resource
Deallocating 80 bytes with 'buffered resource': 0000006D21FDF740
问题
在 C++20 之前,无法调用未首先调用您的 class' 析构函数的释放函数 (operator delete
),从而使您无法清理增加你的 class 拥有的额外明确分配的资源(没有像你的静态指针这样的骇人听闻的代码)
解决方案
如果您可以访问 C++20,那么我鼓励您使用 destroying delete,它是为解决此类问题而创建的。
- 您的 class 可以持有
std::pmr::memory_resource*
的实例(通过构造函数注入)
- 将您的
operator delete
更改为例如 void operator delete(Node *ptr, std::destroying_delete_t) noexcept
destroying_delete
是一个标签,当您使用它时,表示您将负责调用适当的析构函数。
- 派生的 classes 也应该实现类似的删除器。
无需对您的代码进行太多更改,我们可以在 Node
中执行以下操作:
struct Node {
// NOTE: this is actually not necessary..
using allocator_type = std::pmr::polymorphic_allocator<Node>;
void operator delete(Node *ptr, std::destroying_delete_t) noexcept {
deleter(ptr);
}
// don't bother with getters/setters so far..
std::pmr::string name;
template <class TNode >
static TNode *create(std::string_view name, std::pmr::memory_resource *res) {
std::pmr::polymorphic_allocator<TNode> alloc(res);
auto ptr = alloc.allocate(1);
::new (ptr) TNode(alloc, res);
ptr->name = name;
return ptr;
}
virtual ~Node() {
std::cerr << "Destructing node: " << name << std::endl;
}
protected:
Node(const allocator_type& alloc, std::pmr::memory_resource *res)
: name(alloc), s_deleterResource(res) {}
std::pmr::memory_resource *s_deleterResource = nullptr;
template<class TNode>
static void deleter(TNode* ptr) noexcept {
if (ptr->s_deleterResource != nullptr) {
auto* deleterResource = ptr->s_deleterResource;
ptr->~TNode();
std::cerr << "Deleting mem: " << ptr << " using PMR resource\n";
std::pmr::polymorphic_allocator< TNode >(deleterResource)
.deallocate(ptr, 1);
}
else {
std::cerr << "Deleting mem: " << ptr << " using default\n";
::delete ptr;
}
}
};
然后在例如LeafNode
中你可以这样写:
struct LeafNode : Node {
friend struct Node;
using allocator_type = std::pmr::polymorphic_allocator<LeafNode>;
void operator delete(LeafNode *ptr, std::destroying_delete_t) noexcept {
deleter(ptr);
}
protected:
explicit LeafNode(const allocator_type& alloc, std::pmr::memory_resource *res) :
Node(alloc, res), m_payload(77, alloc) { }
std::pmr::vector< uint8_t > m_payload;
};
Live Demo
Allocated: 88 bytes with 'buffered resource': 0x7ffebb5906d0
Allocated: 88 bytes with 'buffered resource': 0x7ffebb590728
Allocated: 77 bytes with 'buffered resource': 0x7ffebb590780
Allocated: 8 bytes with 'buffered resource': 0x7ffebb5907d0
Allocated: 88 bytes with 'buffered resource': 0x7ffebb5907d8
Allocated: 77 bytes with 'buffered resource': 0x7ffebb590830
Allocated: 16 bytes with 'buffered resource': 0x7ffebb590880
Deallocating 8 bytes with 'buffered resource': 0x7ffebb5907d0
Beginning tree deletetion..
Deallocating 77 bytes with 'buffered resource': 0x7ffebb590780
Destructing node: child1
Deleting mem: 0x7ffebb590728 using PMR resource
Deallocating 88 bytes with 'buffered resource': 0x7ffebb590728
Deallocating 77 bytes with 'buffered resource': 0x7ffebb590830
Destructing node: child2
Deleting mem: 0x7ffebb5907d8 using PMR resource
Deallocating 88 bytes with 'buffered resource': 0x7ffebb5907d8
Deallocating 16 bytes with 'buffered resource': 0x7ffebb590880
Destructing node: root
Deleting mem: 0x7ffebb5906d0 using PMR resource
Deallocating 88 bytes with 'buffered resource': 0x7ffebb5906d0
(注意 class 有点大,因为它持有一个指针而不是那个指针 static
)
我想构建一棵由 Node
类型的多态对象组成的树,这些对象是
使用自定义 PMR 分配器分配。
到目前为止,一切正常,但我不知道如何正确删除使用非标准分配器分配的多态对象?我只是想出了一个解决方案来声明
一个静态对象持有对 std::pmr::memory_resource
.. 的引用,但这很讨厌。
是否有任何 "right" 删除自定义分配的多态对象的方法?
这是一个自包含的例子:
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <functional>
#include <memory_resource>
struct Node {
// NOTE: this is actually not necessary..
using allocator_type = std::pmr::polymorphic_allocator<Node>;
void operator delete(void *ptr, std::size_t sz) noexcept {
Node::deleter(ptr, sz);
}
// don't bother with getters/setters so far..
std::pmr::string name;
template <class TNode >
static TNode *create(std::string_view name, std::pmr::memory_resource *res) {
std::pmr::polymorphic_allocator<TNode> alloc(res);
auto ptr = alloc.allocate(1);
::new (ptr) TNode(alloc);
ptr->name = name;
return ptr;
}
virtual ~Node() {
std::cerr << "Destructing node: " << name << std::endl;
}
// NASTY: pointer to memory resource to delete polymorphic objects..
static std::pmr::memory_resource *s_deleterResource;
protected:
Node(const allocator_type& alloc) : name(alloc) {}
static void deleter(void *ptr, std::size_t sz) noexcept {
if (s_deleterResource != nullptr) {
std::cerr << "Deleting mem: " << ptr << " of size: " << sz << " using PMR resource\n";
std::pmr::polymorphic_allocator< char >(s_deleterResource)
.deallocate((char *)ptr, sz);
}
else {
std::cerr << "Deleting mem: " << ptr << " of size: " << sz << " using default\n";
::operator delete(ptr, sz);
}
}
};
decltype (Node::s_deleterResource) Node::s_deleterResource = nullptr;
struct CompoundNode : Node {
friend struct Node;
using allocator_type = std::pmr::polymorphic_allocator<CompoundNode>;
void operator delete(void *ptr, std::size_t sz) noexcept {
Node::deleter(ptr, sz);
}
void addChild(Node *child) {
m_children.push_back(child);
}
~CompoundNode() override {
for(auto child : m_children) {
delete child;
}
}
protected:
explicit CompoundNode(const allocator_type& alloc) :
Node(alloc), m_children(alloc)
{ }
std::pmr::vector< Node * > m_children;
};
struct LeafNode : Node {
friend struct Node;
using allocator_type = std::pmr::polymorphic_allocator<LeafNode>;
void operator delete(void *ptr, std::size_t sz) noexcept {
Node::deleter(ptr, sz);
}
~LeafNode() override {
// NOTE: this is probably won't work since the object m_payload is not yet destroyed
//allocator_type alloc(m_payload.get_allocator());
//alloc.deallocate(this, 1);
}
protected:
explicit LeafNode(const allocator_type& alloc) :
Node(alloc), m_payload(77, alloc) { }
std::pmr::vector< uint8_t > m_payload;
};
// adding verbosity to the existing memory resource
struct VerboseMemResource : public std::pmr::memory_resource {
VerboseMemResource(const char *name, std::pmr::memory_resource *base)
: m_name(name), m_resource(base) { }
private:
void *do_allocate(std::size_t bytes, std::size_t alignment) override {
auto ptr = m_resource->allocate(bytes, alignment);
std::cerr << "Allocated: " << bytes << " bytes with '" << m_name << "': " << ptr << std::endl;
return ptr;
}
void do_deallocate(void *ptr, std::size_t bytes, std::size_t alignment) override {
std::cerr << "Deallocating " << bytes << " bytes with '" << m_name << "': " << ptr << std::endl;
return m_resource->deallocate(ptr, bytes, alignment);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return m_resource->is_equal(other);
}
std::string m_name;
std::pmr::memory_resource *m_resource;
};
int main() try
{
std::array< uint8_t, 1000 > buf;
std::pmr::monotonic_buffer_resource bufferRes(buf.data(), buf.size(),
std::pmr::null_memory_resource());
VerboseMemResource res("buffered resource", &bufferRes);
auto root = Node::create<CompoundNode>("root", &res);
root->addChild(Node::create<LeafNode>("child1", &res));
root->addChild(Node::create<LeafNode>("child2", &res));
// set the pointer to our memory resource for deletion:
Node::s_deleterResource = &res;
std::cerr << "Beginning tree deletetion..\n";
delete root;
Node::s_deleterResource = nullptr;
return 0;
}
catch(std::exception& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
return 1;
}
这是输出:
Allocated: 80 bytes with 'buffered resource': 0000006D21FDF740
Allocated: 80 bytes with 'buffered resource': 0000006D21FDF790
Allocated: 77 bytes with 'buffered resource': 0000006D21FDF7E0
Allocated: 8 bytes with 'buffered resource': 0000006D21FDF830
Allocated: 80 bytes with 'buffered resource': 0000006D21FDF838
Allocated: 77 bytes with 'buffered resource': 0000006D21FDF888
Allocated: 16 bytes with 'buffered resource': 0000006D21FDF8D8
Deallocating 8 bytes with 'buffered resource': 0000006D21FDF830
Beginning tree deletetion..
Deallocating 77 bytes with 'buffered resource': 0000006D21FDF7E0
Destructing node: child1
Deleting mem: 0000006D21FDF790 of size: 80 using PMR resource
Deallocating 80 bytes with 'buffered resource': 0000006D21FDF790
Deallocating 77 bytes with 'buffered resource': 0000006D21FDF888
Destructing node: child2
Deleting mem: 0000006D21FDF838 of size: 80 using PMR resource
Deallocating 80 bytes with 'buffered resource': 0000006D21FDF838
Deallocating 16 bytes with 'buffered resource': 0000006D21FDF8D8
Destructing node: root
Deleting mem: 0000006D21FDF740 of size: 80 using PMR resource
Deallocating 80 bytes with 'buffered resource': 0000006D21FDF740
问题
在 C++20 之前,无法调用未首先调用您的 class' 析构函数的释放函数 (operator delete
),从而使您无法清理增加你的 class 拥有的额外明确分配的资源(没有像你的静态指针这样的骇人听闻的代码)
解决方案
如果您可以访问 C++20,那么我鼓励您使用 destroying delete,它是为解决此类问题而创建的。
- 您的 class 可以持有
std::pmr::memory_resource*
的实例(通过构造函数注入) - 将您的
operator delete
更改为例如void operator delete(Node *ptr, std::destroying_delete_t) noexcept
destroying_delete
是一个标签,当您使用它时,表示您将负责调用适当的析构函数。
- 派生的 classes 也应该实现类似的删除器。
无需对您的代码进行太多更改,我们可以在 Node
中执行以下操作:
struct Node {
// NOTE: this is actually not necessary..
using allocator_type = std::pmr::polymorphic_allocator<Node>;
void operator delete(Node *ptr, std::destroying_delete_t) noexcept {
deleter(ptr);
}
// don't bother with getters/setters so far..
std::pmr::string name;
template <class TNode >
static TNode *create(std::string_view name, std::pmr::memory_resource *res) {
std::pmr::polymorphic_allocator<TNode> alloc(res);
auto ptr = alloc.allocate(1);
::new (ptr) TNode(alloc, res);
ptr->name = name;
return ptr;
}
virtual ~Node() {
std::cerr << "Destructing node: " << name << std::endl;
}
protected:
Node(const allocator_type& alloc, std::pmr::memory_resource *res)
: name(alloc), s_deleterResource(res) {}
std::pmr::memory_resource *s_deleterResource = nullptr;
template<class TNode>
static void deleter(TNode* ptr) noexcept {
if (ptr->s_deleterResource != nullptr) {
auto* deleterResource = ptr->s_deleterResource;
ptr->~TNode();
std::cerr << "Deleting mem: " << ptr << " using PMR resource\n";
std::pmr::polymorphic_allocator< TNode >(deleterResource)
.deallocate(ptr, 1);
}
else {
std::cerr << "Deleting mem: " << ptr << " using default\n";
::delete ptr;
}
}
};
然后在例如LeafNode
中你可以这样写:
struct LeafNode : Node {
friend struct Node;
using allocator_type = std::pmr::polymorphic_allocator<LeafNode>;
void operator delete(LeafNode *ptr, std::destroying_delete_t) noexcept {
deleter(ptr);
}
protected:
explicit LeafNode(const allocator_type& alloc, std::pmr::memory_resource *res) :
Node(alloc, res), m_payload(77, alloc) { }
std::pmr::vector< uint8_t > m_payload;
};
Live Demo
Allocated: 88 bytes with 'buffered resource': 0x7ffebb5906d0
Allocated: 88 bytes with 'buffered resource': 0x7ffebb590728
Allocated: 77 bytes with 'buffered resource': 0x7ffebb590780
Allocated: 8 bytes with 'buffered resource': 0x7ffebb5907d0
Allocated: 88 bytes with 'buffered resource': 0x7ffebb5907d8
Allocated: 77 bytes with 'buffered resource': 0x7ffebb590830
Allocated: 16 bytes with 'buffered resource': 0x7ffebb590880
Deallocating 8 bytes with 'buffered resource': 0x7ffebb5907d0
Beginning tree deletetion..
Deallocating 77 bytes with 'buffered resource': 0x7ffebb590780
Destructing node: child1
Deleting mem: 0x7ffebb590728 using PMR resource
Deallocating 88 bytes with 'buffered resource': 0x7ffebb590728
Deallocating 77 bytes with 'buffered resource': 0x7ffebb590830
Destructing node: child2
Deleting mem: 0x7ffebb5907d8 using PMR resource
Deallocating 88 bytes with 'buffered resource': 0x7ffebb5907d8
Deallocating 16 bytes with 'buffered resource': 0x7ffebb590880
Destructing node: root
Deleting mem: 0x7ffebb5906d0 using PMR resource
Deallocating 88 bytes with 'buffered resource': 0x7ffebb5906d0
(注意 class 有点大,因为它持有一个指针而不是那个指针 static
)