новый не вызван, но память выделена

Я написал простую 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;
}

Моя проблема в том, что, хотя ‘hello’ уже вставлено во второй раз, когда попытка его вставки, и, таким образом, new больше не вызывается, много памяти выделяется и освобождается. Эта сумма увеличивается по мере того, как я увеличивает значение max 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 () является постоянным. Тогда почему и как распределяется и освобождается вся эта память?

5

Решение

Каждый раз, когда вы звоните insertПередаешь const char[6], но это ожидает const std::string&и поэтому каждая итерация создает временную std::string, который затем передается в функцию, а затем уничтожается перед следующей итерацией. Это разъясняет 10000 распределений и освобождений, оставляя только 11, которые, по-видимому, являются вашим распределением узла, а также все, что угодно std::map внутренне, и несколько других мест, которые я пропустил (например, копии строк или карты)

Контейнер может выделять память, даже если он не содержит элементов, но я бы сказал, что он должен был быть спроектирован иначе, и был бы удивлен, если бы любая крупная реализация контейнера сделала это. (Хотя deque может быть исключением)

13

Другие решения

std::map будет выделять свою собственную память динамически, и вы будете создавать новую каждый раз, когда вы звоните get_child(), Сколько памяти он выделяет при использовании конструктора по умолчанию, я не могу сказать, но это, вероятно, что-то. Только потому, что ты не звонишь new не означает, что другие типы, созданные вашим классом, не делают.

Также, std::map не собирается выделять совершенно новое хранилище кучи для каждого вставленного элемента. Это было бы ужасно неэффективно. Он имеет некоторый внутренний алгоритм для увеличения своего резервного хранилища, когда это необходимо, и он, безусловно, выделит больше, чем необходимо для размещения этого нового элемента.

5

По вопросам рекламы [email protected]