5

簡単なTrie実装を作成しました。ソースコードは次のとおりです。

#include <string>
#include <map>

typedef unsigned int uint;

class Trie {
public:
    class Node {
    public:
            Node(const char & _value);
            ~Node();
            char get_value() const;
            void set_marker(const uint & _marker);
            uint get_marker() const;
            bool add_child(Node * _child);
            Node * get_child(const char & _value) const;
            void clear();
    private:
            char m_value;
            uint m_marker;
            std::map<char, Node *> m_children;
    };

    Trie();
    ~Trie();
    bool insert(const std::string & _str);
    bool find(const std::string & _str) const;
private:
    Node * m_root;
};
// - implementation (in a different file)
using namespace std;

Trie::Node::Node(const char & _value) :
            m_value(_value), m_marker(0), m_children() {
}

Trie::Node::~Node() {
    clear();
}

void Trie::Node::clear() {
    map<char, Node*>::const_iterator it;
    for (it = m_children.begin(); it != m_children.end(); ++it) {
            delete it->second;
    }
}

void Trie::Node::set_marker(const uint & _marker) {
    m_marker = _marker;
}

uint Trie::Node::get_marker() const {
    return m_marker;
}

char Trie::Node::get_value() const {
    return m_value;
}

Trie::Node * Trie::Node::get_child(const char & _value) const {
    map<char, Node*>::const_iterator it;
    bool found = false;
    for (it = m_children.begin(); it != m_children.end(); ++it) {
            if (it->first == _value) {
                    found = true;
                    break;
            }
    }
    if (found) {
            return it->second;
    }
    return NULL;
}

bool Trie::Node::add_child(Node * _child) {
    if (_child == NULL) {
            return false;
    }
    if (get_child(_child->get_value()) != NULL) {
            return false;
    }
    m_children.insert(pair<char, Node *>(_child->get_value(), _child));
    return true;
}

Trie::Trie() :
            m_root(new Node('\0')) {
}

Trie::~Trie() {
    delete m_root;
}

bool Trie::insert(const string & _str) {
    Node * current = m_root;
    bool inserted = false;
    for (uint i = 0; i < _str.size(); ++i) {
            Node * child = current->get_child(_str[i]);
            if (child == NULL) {
                    child = new Node(_str[i]);
                    current->add_child(child);
                    inserted = true;
            }
            current = child;
    }
    if (current->get_marker() != _str.size()) {
            current->set_marker(_str.size());
            inserted = true;
    }
    return inserted;
}

bool Trie::find(const std::string & _str) const {
    Node * current = m_root;
    bool found = false;
    for (uint i = 0; i < _str.size(); ++i) {
            Node * child = current->get_child(_str[i]);
            if (child == NULL) {
                    break;
            } else {
                    current = child;
            }
    }
    if (current->get_marker() == _str.size()) {
            found = true;
    }
    return found;
}

ここに私のテストプログラムがあります:

#include <iostream>
#include <sstream>
#include "Trie.h"

int main() {
    Trie t;
    for (unsigned int i = 0; i < 10000; ++i) {
            t.insert("hello");
    }
    return 0;
}

私の問題は、挿入が 2 回目に試行されたときに「hello」が既に挿入されているため、new呼び出されなくなったにもかかわらず、大量のメモリが割り当てられ、割り当てが解除されていることです。この量は、最大 i の値が増加するにつれて増加します。たとえば、上記の場合、valgrind は次の出力を提供します。

==10322== HEAP SUMMARY:
==10322==     in use at exit: 0 bytes in 0 blocks
==10322==   total heap usage: 10,011 allocs, 10,011 frees, 300,576 bytes allocated

Node() コンストラクターの呼び出し回数が一定であることを確認しました。それでは、なぜ、どのようにすべてのメモリが割り当てられ、割り当て解除されるのでしょうか?

4

2 に答える 2

13

を呼び出すたびinsertに を渡しますがconst char[6]、それは を想定しているconst std::string&ため、すべての反復で一時的な が作成std::stringされ、それが関数に渡され、次の反復の前に破棄されます。これにより、10000 の割り当てと割り当て解除が明確になり、11 だけが残ります。これは、おそらくノードの割り当てであり、std::map内部で行われるものと、私が見落としていた他のいくつかの場所 (文字列やマップのコピーなど) です。

コンテナーは、要素が含まれていなくてもメモリを割り当てることができます。(dequeは例外かもしれませんが)

于 2013-01-08T19:44:15.737 に答える
5

std::mapは独自のメモリを動的に割り当て、 を呼び出すたびに新しいメモリを作成しますget_child()。デフォルトのコンストラクターを使用するときに割り当てるメモリの量はわかりませんが、おそらく何か. 呼び出さないからといってnew、クラスによって作成された他の型が呼び出されないわけではありません。

また、std::map挿入された要素ごとにまったく新しいヒープ ストアを割り当てるわけではありません。それは非常に非効率的です。必要に応じてバッキング ストアを拡張するための内部アルゴリズムがあり、その 1 つの新しい要素に合わせて必要以上に割り当てます。

于 2013-01-08T19:24:35.590 に答える