【问题标题】:Can someone explain me how to dynamically allocate memory for a vectors of pointers有人可以解释一下如何为指针向量动态分配内存吗
【发布时间】:2021-09-15 12:05:28
【问题描述】:

谁能解释一下给定的代码。我无法理解使用vector <treenode < int > * > child; 的目的 它正在创建一个名为 child 的向量,但在声明中使用 treenode 有什么意义。 我也在这个问题中使用了模板(以防信息帮助)

class treenode
{
    public:
    int data;
                
    vector<treenode<int>*> child;
    treenode(T data)
    {
        this ->data = data;
    }
};

【问题讨论】:

  • 它是指向treenode&lt;int&gt; 的指针的vector(我假设是std::vector)。我猜源代码有一个树结构,它由保存整数数据的树节点组成。这些树节点的类型为treenode&lt;int&gt;。如果您需要跟踪某些树节点,可以将指向这些节点的指针存储在 std::vector 中。你可能见过的std:: 的意思是“属于std 命名空间。
  • 是的,你是对的,其实我想问一下为什么它在声明中使用了“treenode”,即vector*> child。因为vector声明的语法是vector* vec_name
  • 不是。您只是以这种方式声明了一个指向整数向量的指针。代码中声明的是指向树节点的指针向量......但它假设树节点是模板很奇怪,请参阅en.cppreference.com/w/cpp/container/vector。对于模板类型,template-name &lt;parameter-list&gt; 是指定类型名称的语法。

标签: c++ memory vector dynamic tree


【解决方案1】:

与其发表其他评论,您可能不知道模板可以根据其他模板定义,理论上可以达到任何水平 - 但受机器限制。例如vector&lt;T&gt; 是类型为T 的对象向量。但是T本身可以是一个模板。

在这种情况下,向量模板本身就是一个模板。 treenode&lt;int&gt; *.

好的,你可以拥有vectorintstring,任何你喜欢的东西。在您的示例中,编码器具有一个树结构,其中包含他们称为treenode 的节点。恕我直言,将节点称为“分支”(如树的分支)会更有意义。

无论如何,向量通常用于跟踪需要注意的事项列表。可以是整数,数据结构的一部分,任何东西。

【讨论】:

    【解决方案2】:

    它看起来像不完整的代码。应该是这样的

    template <class T>
    class treenode
    

    因为vector&lt;treenode&lt;int&gt;*&gt; child 是模板类型vector 带有类型参数treenode&lt;int&gt;*treenode 必须是模板才能允许模板参数&lt;int&gt;。我也质疑这段代码的合理性。如果它描述了一个包含T类型数据的树节点(可能是int),那么

    template <class T>
    class treenode
    {
    public:
        T data;
                    
        vector<treenode<T>*> child;
    
        treenode(T data)  // (or T&? or const T&? or T&&?)
        {
            this ->data = data;
        }
    

    声明template &lt;class T&gt; class treenode 上下文中的vector&lt;treenode*&gt; 是指向treenode&lt;T&gt; 的指针向量。

    现在考虑以下代码。我向treenode 添加了一些有用的东西来展示如何分配内存。为避免混淆,新类型 Data 将用作 T 而不是 int

    #include <iostream>
    #include <vector>
    // using namespace std;
    
    template <class T>
    class treenode
    {
    public:
        T data; // with using `namespace std;` this can be a problem, 
                // because there is `std::data`
       
        // treenode<T> is class containing this, so we can refer it 
        // just as treenode             
        std::vector<treenode*> child; 
    
        treenode(const T& d) : data(d)  
        // if you use data as argument name, 
        // you are shadowing treenode::data member
        {
        }
        
        void add_node(const T& d)
        {
            // here we create a pointer with value returned by new
            // treenode was initialized by d within new expression.
            child.emplace_back(new treenode{d});
        }
        
        ~treenode() {
            // vector contained raw pointers we allocated memory for.
            // this avoids leaks.
            for(auto& p : child)
                delete p;
        }
    };
    
    template <class T>
    struct Data {
        T value;
        
        Data(T v) : value(v) {std::cout << "Hello, data = " << v << std::endl;}
        ~Data() {std::cout << "Bye, data = " << value << std::endl;}
    };
    
    int main()
    {
        // this is legal after C++17, template parameters can be deducted
        treenode a = {Data{42}}; 
    
        // It's same as this C++11 declaration
        //    treenode<Data<int>> a  = {42};
        
        a.add_node(34);
        
        return 0;
    }
    

    我们可以使用智能指针,避免使用 new

    #include <iostream>
    #include <vector>
    #include <memory>
    
    template <class T>
    class treenode
    {
    public:
        T data;
                    
        std::vector<std::unique_ptr<treenode>> child;
    
        treenode(const T& d) : data(d) {}
        
        void add_node(const T& d)
        {
            child.emplace_back(std::make_unique<treenode>(d));
        }
        
        ~treenode() { } 
        // no action needed,  pointers clean up after themselves!
    };
    

    你能猜出它会做什么吗?

    Hello, data = 42
    Bye, data = 42
    Hello, data = 34
    Bye, data = 34
    // a is getting destroyed.
    Bye, data = 34
    Bye, data = 42
    

    一些数据死在那里。那是因为这个设置创建了一些临时的 T = Data,Data 被复制或移动了,我们没有看到它。没关系,如果treenode 的Tint。对于复杂类型,如果我们的数据没有正确的复制\移动操作,这可能是一个问题。让我们至少添加构造函数。

    template <class T>
    struct Data {
        T value;
        
        Data(const T& v) : value(v) 
        {std::cout << "Hello, data = " << v << std::endl;}
        
        // move constructor
        Data(Data&& v) : value(std::move(v.value))  
        {std::cout << "Moved data = " << v.value << std::endl; v.value = T{}; }
        // copy constructor
        Data(const Data& v) : value(v.value) 
        { std::cout << "Copied data = " << v.value << std::endl; }
        
        ~Data() {std::cout << "Bye, data = " << value << std::endl;}
    };
    

    现在我们可以看到复制操作了:

    Hello, data = 42
    Copied data = 42
    Bye, data = 42
    Hello, data = 34
    Copied data = 34
    Bye, data = 34
    Bye, data = 34
    Bye, data = 42
    

    对于简单的“平面”类型,这可以很好,复制和移动操作对它们来说是相等的。但是对于某些容器来说,创建我们永远不会再次使用的临时对象然后复制分配的内存是昂贵的。同意“移动”操作将分配资源的所有权转移到目标对象并修改源,因此它不会释放它们。之后源对象将变得无效。我们必须在 treenode 类中添加对 T 的移动和复制的支持。

    template <class T>
    class treenode
    {
    public:
        T data;
                    
        std::vector<std::unique_ptr<treenode>> child;
    
        treenode(const T& d) : data(d) {}
        treenode(T&& d) : data(std::move(d)) {}
        
        void add_node(const T& d)
        {
            child.emplace_back(std::make_unique<treenode>(d));
        }
        
        void add_node(T&& d)
        {
            child.emplace_back(std::make_unique<treenode>(std::move(d)));
        }
        
        ~treenode() {
        }
    };
    

    现在一切都加起来了:

    Hello, data = 42   // we created Data(42)
    Moved data = 42    // its value was moved
    Bye, data = 0      // it was destroyed.
    
    Hello, data = 34
    Moved data = 34
    Bye, data = 0
    
    Bye, data = 34    // a is being destroyed
    Bye, data = 42
    

    拥有两个add_node 实现是多余的。在某些情况下,如果涉及多个参数,可能会导致声明数量呈指数级增长。我们只需要使用单个perfect forwarding

    template <class V = T>
    void add_node(V&& d) {
        child.emplace_back(std::make_unique<treenode>(std::forward<V>(d)));
    }  
    

    这将导致这样的操作:

    Data c {63};
    a.add_node(c);
    

    创建副本:

    Hello, data = 63
    Copied data = 63
    

    最后需要为树节点定义一个默认构造函数,以及一个移动构造函数,以完成其对Rule of 5 的设计。复制构造函数可能正确也可能不正确,这取决于我们是允许创建树的副本,仅创建节点值的副本,还是根本不允许。

    template <class T>
    class treenode
    {
    public:
        T data;
                    
        std::vector<std::unique_ptr<treenode>> child;
    
        treenode(const T& d) : data(d) {}
        treenode(T&& d) : data(std::move(d)) {}
        
        treenode() : data() {}
        
        // treenode(treenode&& other): 
        //  data(std::move(other.data)), child(std::move(other.child)) {}
        treenode(treenode&& other) = default;
        
        // would not copy children, std::unique_ptr is not copyable
        treenode(const treenode& other) : data(other.data) {}
        
        // it's default because we move all  members.
        treenode& operator=(treenode&& other) = default;
            
        // copies data's value, doesn't alter children
        treenode& operator=(const treenode& other) 
        { this->data = other.data; return *this; }
            
        template <class V = T>
        void add_node(V&& d) {
            child.emplace_back(std::make_unique<treenode>(std::forward<V>(d)));
        }     
        
        ~treenode() = default;
    };
    

    数据也需要它的特殊成员:

    template <class T>
    struct Data {
        T value;
        
        Data(const T& v) : value(v) 
        { std::cout << "Hello, data = " << v << std::endl;}
        
        Data(Data&& v) : value(std::move(v.value)) 
        { std::cout << "Moved data = " << v.value << std::endl; v.value = T{}; }
        Data(const Data& v) : value(v.value) 
        { std::cout << "Copied data = " << v.value << std::endl; }
        
        Data() : T() {}
        ~Data() {std::cout << "Bye, data = " << value << std::endl;}
        
        Data& operator=(Data&& other) = default;
        Data& operator=(const Data& other) = default;
    };
    

    【讨论】:

      猜你喜欢
      • 2012-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-16
      • 1970-01-01
      • 1970-01-01
      • 2015-10-06
      • 1970-01-01
      相关资源
      最近更新 更多