如何避免在 C++ 中使用 new 运算符?
How to avoid using new operator in C++?
我有一个 C++ 程序可以为文件中的所有字符创建霍夫曼代码。它运行良好,但我想在不使用 new 运算符的情况下创建节点,因为我知道你不应该使用它。我尝试使用向量全局变量来保存节点,但这不起作用。
std::vector<Node> nodes;
Node* create_node(unsigned char value, unsigned long long counter, Node* left, Node* right) {
Node temp;
temp.m_value = value;
temp.m_counter = counter;
temp.m_left = left;
temp.m_right = right;
nodes.push_back(temp);
return &nodes[nodes.size() - 1];
}
编辑:我添加了更多代码,我没有真正解释什么不起作用。问题出在 generate_code()
,它永远不会到达 nullptr。我也尝试使用 Node 而不是 Node*,但同样的事情发生了。
void generate_code(Node* current, std::string code, std::map<unsigned char, std::string>& char_codes) {
if (current == nullptr) {
return;
}
if (!current->m_left && !current->m_right) {
char_codes[current->m_value] = code;
}
generate_code(current->m_left, code + "0", char_codes);
generate_code(current->m_right, code + "1", char_codes);
}
void huffman(std::ifstream& file) {
std::unordered_map<unsigned char, ull> char_frequency;
load_data(file, char_frequency);
std::priority_queue<Node*, std::vector<Node*>, Comparator> queue;
for (auto& node : char_frequency) {
queue.push(create_node(node.first, node.second, nullptr, nullptr));
}
while (queue.size() != 1) {
Node* left = queue.top();
queue.pop();
Node* right = queue.top();
queue.pop();
auto counter = left->m_counter + right->m_counter;
queue.push(create_node('[=11=]', counter, left, right));
}
std::map<unsigned char, std::string> char_codes;
Node* root = queue.top();
generate_code(root, "", char_codes);
for (auto& i : char_codes) {
std::cout << +i.first << ": " << i.second << "\n";
}
}
一般的答案当然是使用智能指针,比如std::shared_ptr<Node>
。
也就是说,使用常规指针并没有那么糟糕,尤其是当您从外部隐藏所有指针时。我不同意“你不应该使用 new
”,更像是“你应该意识到,如果你这样做,你必须确保不会造成内存泄漏”。
无论如何,对于像您所做的事情,尤其是您的向量,您根本不需要实际的指针。简单地为你的向量存储一个索引,并用 int
替换每个出现的 Node*
,有点像:
class Node
{
public:
// constructors and accessors
private:
ValueType value;
int index_left;
int index_right;
}
我在这里使用有符号整数作为索引,以便允许为不存在的引用存储 -1,类似于空指针。
请注意,这仅在向量中没有任何内容被删除时才有效,至少在所有内容都被销毁之前不会。如果灵活性是关键,您需要某种指导。
另请注意,您不应将矢量作为 global variable。相反,有一个包装 class,其中 Node
是一个内部 class,有点像这样:
class Tree
{
public:
class Node
{
...
};
// some methods here
private:
vector<Node> nodes;
}
通过这种方法,您可以更好地封装您的 Node
class。 Tree
很可能是 friend
。每个 Node
都会存储对它所属的 Tree
的引用。
另一种可能性是使向量成为 Node
的静态成员,但我不建议这样做。如果向量是 Node
的静态成员或全局对象,在这两种情况下,您创建的所有树都在一个大容器中,这意味着当您不这样做时,您无法从其中一个释放内存不再需要它了。
虽然这在技术上不是内存泄漏,但实际上,它可以很容易地作为一个内存泄漏。
另一方面,如果它存储为 Tree
对象的成员,则一旦删除该对象,内存就会自动释放。
but I want to create nodes without using new operator because I know that you shouldn't use it.
不鼓励直接使用 new
的原因是所有权语义(即谁负责相应的 delete
)不明确。
C++ 标准库为此提供了 Dynamic memory management 实用程序,尤其是智能指针。
所以我认为您的创建函数应该如下所示:
std::unique_ptr<Node> create_node(unsigned char value, unsigned long long counter, Node* left, Node* right) {
std::unique_ptr<Node> temp = std::make_unique<Node>();
temp->m_value = value;
temp->m_counter = counter;
temp->m_left = left;
temp->m_right = right;
return temp;
}
这样很明显,调用者获得了新创建的 Node
实例的所有权。
我有一个 C++ 程序可以为文件中的所有字符创建霍夫曼代码。它运行良好,但我想在不使用 new 运算符的情况下创建节点,因为我知道你不应该使用它。我尝试使用向量全局变量来保存节点,但这不起作用。
std::vector<Node> nodes;
Node* create_node(unsigned char value, unsigned long long counter, Node* left, Node* right) {
Node temp;
temp.m_value = value;
temp.m_counter = counter;
temp.m_left = left;
temp.m_right = right;
nodes.push_back(temp);
return &nodes[nodes.size() - 1];
}
编辑:我添加了更多代码,我没有真正解释什么不起作用。问题出在 generate_code()
,它永远不会到达 nullptr。我也尝试使用 Node 而不是 Node*,但同样的事情发生了。
void generate_code(Node* current, std::string code, std::map<unsigned char, std::string>& char_codes) {
if (current == nullptr) {
return;
}
if (!current->m_left && !current->m_right) {
char_codes[current->m_value] = code;
}
generate_code(current->m_left, code + "0", char_codes);
generate_code(current->m_right, code + "1", char_codes);
}
void huffman(std::ifstream& file) {
std::unordered_map<unsigned char, ull> char_frequency;
load_data(file, char_frequency);
std::priority_queue<Node*, std::vector<Node*>, Comparator> queue;
for (auto& node : char_frequency) {
queue.push(create_node(node.first, node.second, nullptr, nullptr));
}
while (queue.size() != 1) {
Node* left = queue.top();
queue.pop();
Node* right = queue.top();
queue.pop();
auto counter = left->m_counter + right->m_counter;
queue.push(create_node('[=11=]', counter, left, right));
}
std::map<unsigned char, std::string> char_codes;
Node* root = queue.top();
generate_code(root, "", char_codes);
for (auto& i : char_codes) {
std::cout << +i.first << ": " << i.second << "\n";
}
}
一般的答案当然是使用智能指针,比如std::shared_ptr<Node>
。
也就是说,使用常规指针并没有那么糟糕,尤其是当您从外部隐藏所有指针时。我不同意“你不应该使用 new
”,更像是“你应该意识到,如果你这样做,你必须确保不会造成内存泄漏”。
无论如何,对于像您所做的事情,尤其是您的向量,您根本不需要实际的指针。简单地为你的向量存储一个索引,并用 int
替换每个出现的 Node*
,有点像:
class Node
{
public:
// constructors and accessors
private:
ValueType value;
int index_left;
int index_right;
}
我在这里使用有符号整数作为索引,以便允许为不存在的引用存储 -1,类似于空指针。
请注意,这仅在向量中没有任何内容被删除时才有效,至少在所有内容都被销毁之前不会。如果灵活性是关键,您需要某种指导。
另请注意,您不应将矢量作为 global variable。相反,有一个包装 class,其中 Node
是一个内部 class,有点像这样:
class Tree
{
public:
class Node
{
...
};
// some methods here
private:
vector<Node> nodes;
}
通过这种方法,您可以更好地封装您的 Node
class。 Tree
很可能是 friend
。每个 Node
都会存储对它所属的 Tree
的引用。
另一种可能性是使向量成为 Node
的静态成员,但我不建议这样做。如果向量是 Node
的静态成员或全局对象,在这两种情况下,您创建的所有树都在一个大容器中,这意味着当您不这样做时,您无法从其中一个释放内存不再需要它了。
虽然这在技术上不是内存泄漏,但实际上,它可以很容易地作为一个内存泄漏。
另一方面,如果它存储为 Tree
对象的成员,则一旦删除该对象,内存就会自动释放。
but I want to create nodes without using new operator because I know that you shouldn't use it.
不鼓励直接使用 new
的原因是所有权语义(即谁负责相应的 delete
)不明确。
C++ 标准库为此提供了 Dynamic memory management 实用程序,尤其是智能指针。
所以我认为您的创建函数应该如下所示:
std::unique_ptr<Node> create_node(unsigned char value, unsigned long long counter, Node* left, Node* right) {
std::unique_ptr<Node> temp = std::make_unique<Node>();
temp->m_value = value;
temp->m_counter = counter;
temp->m_left = left;
temp->m_right = right;
return temp;
}
这样很明显,调用者获得了新创建的 Node
实例的所有权。