簡體   English   中英

刪除使用 C++17 pmr memory 資源分配的多態對象

[英]Deleting polymorphic objects allocated with C++17 pmr memory resource

我想建立一個由Node類型的多態對象組成的樹,這些對象使用自定義 PMR 分配器進行分配。

到目前為止,一切都運行良好,但我無法弄清楚如何正確刪除使用非標准分配器分配的多態對象? 我只提出了一個解決方案來聲明一個 static object 持有對std::pmr::memory_resource的引用..但這很討厭。 是否有任何“正確”的方法來刪除自定義分配的多態對象?

這是一個自包含的示例:

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

這是 output:

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 之前,沒有辦法調用不首先調用類的析構函數的釋放函數( operator delete ),使您無法清理類擁有的額外顯式分配的資源(沒有 hacky代碼就像你的靜態指針)

解決方案

如果您可以訪問 C++20,那么我鼓勵您使用銷毀刪除來解決此類問題。

  • 您的類可以保留std::pmr::memory_resource*的實例(通過構造函數注入)
  • 將您的operator delete更改為例如void operator delete(Node *ptr, std::destroying_delete_t) noexcept
    • destroying_delete是一個標記,當您使用它時,表明您將負責調用適當的析構函數。
  • 派生類也應該實現類似的刪除器。

無需對代碼進行太多更改,我們可以在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;
};

現場演示

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

(請注意,該類有點大,因為它持有一個指針而不是該指針是static

實際上,我找到了與 AndyG 所做的相同的方法,但在 C++17 中(不使用討厭的 static 成員Node::s_deleterResource並且沒有丟失的std::destroying_delete_t destroying_delete_t )。

這個想法是引入一個純虛擬 function size() ,它為每個子類返回*this的大小。 然后,顯式調用子的析構函數並釋放所需的字節數。 下面是修改后的演示程序。 在那里,我介紹了一個頂級RootNode ,它是唯一可以在堆棧上創建的節點類型:所有其他節點都是使用給定的分配器創建的,如前所述:

#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <functional>
#include <memory_resource>

struct CompoundNode;

struct Node {

    friend struct CompoundNode;
    // NOTE: this is actually not necessary..
    using allocator_type = std::pmr::polymorphic_allocator<Node>;

    // don't bother with getters/setters so far..
    std::pmr::string name;

    virtual ~Node() {
        std::cerr << "Destructing node: " << name << std::endl;
    }
    // function returning size of this object
    virtual size_t size() const noexcept = 0;

protected:
    Node(const allocator_type& alloc) : name(alloc) {}
};

struct CompoundNode : Node {

    using allocator_type = std::pmr::polymorphic_allocator<CompoundNode>;

    size_t size() const noexcept override {
        return sizeof(CompoundNode);
    }

    template <class TNode >
    TNode *addChild(std::string_view name) {

        std::pmr::polymorphic_allocator<TNode> alloc(m_children.get_allocator());
        auto ptr = alloc.allocate(1);
        ::new (ptr) TNode(alloc);
        ptr->name = name;
        m_children.push_back(ptr);
        return ptr;
    }

    ~CompoundNode() override {

        std::cerr << "Destructing CompoundNode..\n";
        std::pmr::polymorphic_allocator<char> alloc(m_children.get_allocator());

        for(auto child : m_children)
        {
            std::cerr << "Destructing child: " << child->name << '\n';
            auto sz = child->size();             // get our size
            child->~Node();                      // call virtual destructor
            alloc.deallocate((char *)child, sz); // free memory
        }
    }

protected:
    explicit CompoundNode(const allocator_type& alloc) :
        Node(alloc), m_children(alloc)
    { }

    std::pmr::vector< Node * > m_children;
};

struct LeafNode : Node {
    friend struct CompoundNode;

    using allocator_type = std::pmr::polymorphic_allocator<LeafNode>;

    size_t size() const noexcept override {
        return sizeof(LeafNode);
    }

    ~LeafNode() override = default;

protected:
    explicit LeafNode(const allocator_type& alloc) :
        Node(alloc), m_payload(77, alloc) { }

    std::pmr::vector< uint8_t > m_payload;
};

struct RootNode : CompoundNode
{
    //! NOTE: root node does not use a polymorphic allocator itself:
    //! it only provides it for derived classes!
    explicit RootNode(std::pmr::memory_resource *mem) noexcept :
            CompoundNode(mem) {
        name = "root";
    }

    ~RootNode() override = default;
};


// 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);

    {
        RootNode root(&res);
        auto comp = root.addChild<CompoundNode>("compound");
        comp->addChild<LeafNode>("child1");
        comp->addChild<LeafNode>("child2");
    }

    return 0;
}
catch(std::exception& ex) {
    std::cerr << "Exception: " << ex.what() << std::endl;
    return 1;
}

以及由此產生的 output:

Allocated: 80 bytes with 'buffered resource': 000000874DAFF900
Allocated: 8 bytes with 'buffered resource': 000000874DAFF950
Allocated: 80 bytes with 'buffered resource': 000000874DAFF958
Allocated: 77 bytes with 'buffered resource': 000000874DAFF9A8
Allocated: 8 bytes with 'buffered resource': 000000874DAFF9F8
Allocated: 80 bytes with 'buffered resource': 000000874DAFFA00
Allocated: 77 bytes with 'buffered resource': 000000874DAFFA50
Allocated: 16 bytes with 'buffered resource': 000000874DAFFAA0
Deallocating 8 bytes with 'buffered resource': 000000874DAFF9F8
Destructing CompoundNode..
Destructing child: compound
Destructing CompoundNode..
Destructing child: child1
Deallocating 77 bytes with 'buffered resource': 000000874DAFF9A8
Destructing node: child1
Deallocating 80 bytes with 'buffered resource': 000000874DAFF958
Destructing child: child2
Deallocating 77 bytes with 'buffered resource': 000000874DAFFA50
Destructing node: child2
Deallocating 80 bytes with 'buffered resource': 000000874DAFFA00
Deallocating 16 bytes with 'buffered resource': 000000874DAFFAA0
Destructing node: compound
Deallocating 80 bytes with 'buffered resource': 000000874DAFF900
Deallocating 8 bytes with 'buffered resource': 000000874DAFF950
Destructing node: root

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM