【发布时间】:2016-04-12 03:55:20
【问题描述】:
我一直在阅读和试验标准库的智能指针 unique_ptr 和 shared_ptr 虽然它们显然是很多原始指针可能被视为危险的情况的绝佳替代品,但我不确定它们在实现时的用途数据结构。
为了进行实验,我编写了一个使用 shared_ptr 的哈希映射示例 - 根据 Meyer 的 Effective Modern C++,它的大小大约是 unique_ptr 的两倍。出于这个原因,我想使用 unique_ptr,但由于我在 Add 函数中执行的操作、更新和复制,我有点难过。
有人对这个问题有什么建议吗?是否应该使用原始指针来编写数据结构?
#pragma once
#include "core.h"
const int TABLE_SIZE = 256;
template<typename K>
class HashKey {
public:
unsigned long operator()(const K& p_key) const {
return (p_key) % TABLE_SIZE;
}
};
template<typename K, typename T>
class HashNode {
public:
K m_key;
T m_value;
std::shared_ptr<HashNode> next = nullptr;
};
template<typename K, typename T, typename F = HashKey<K>>
class HashMap {
public:
std::array< std::shared_ptr< HashNode<K, T> >, 128 > m_table;
F m_hash_function;
int m_elem_count{ 0 };
void Add(K p_key, T p_value);
};
template<typename K, typename T, typename F = HashKey<K>>
void HashMap<K, T, F>::Add(K p_key, T p_value)
{
unsigned long key = m_hash_function(p_key);
std::shared_ptr<HashNode<K, T>> new_node = std::make_shared<HashNode<K, T>>();
new_node->m_key = p_key;
new_node->m_value = p_value;
if (m_table[key] == nullptr) {
/* only item in the bucket */
m_table[key] = std::move(new_node);
m_elem_count++;
}
else {
/* check if item exists so it is replaced */
std::shared_ptr< HashNode<K, T> > current = m_table[key];
std::shared_ptr< HashNode<K, T> > previous = m_table[key];
while (current != nullptr && p_key != current->m_key ) {
previous = current;
current = current->next;
}
if (current == nullptr) {
previous->next = new_node;
//current = new_node;
m_elem_count++;
}
else {
current->m_value = p_value;
}
}
}
void TestHashMap() {
HashMap<int, std::string> hash_map;
hash_map.Add(1, "one");
hash_map.Add(2, "does");
hash_map.Add(3, "not");
hash_map.Add(50, "simply");
hash_map.Add(11, "waltz");
hash_map.Add(11, "into");
hash_map.Add(191, "mordor");
std::cout << hash_map.m_elem_count << std::endl;
}
【问题讨论】:
-
一般来说,您应该从所有权的角度来考虑新的智能指针,而不是简单地自动删除指针。一个“资源”可以同时拥有多个所有者 (
std::shared_ptr) 还是一次只有一个所有者 (std::unique_ptr)。 -
我不知道你为什么觉得这里需要指针——或者
std::array,就此而言。为什么不按照std::vector<HashNode<K, T>>来实现哈希映射?一般来说,这个问题让我有点困惑:你应该做的大部分事情是编写数据结构(以及它们的算法)。因此,对于数据结构,您当然会使用智能指针,而不是哑指针——那么,您还会将智能指针用于什么用途呢?没有别的了。 -
在树形数据结构中编写诸如旋转之类的代码时,我倾向于进行微优化,这通常包括在他们放弃概念所有权后的几条指令中使用指针。只要我使用原始指针并且我理解代码的所有权语义,我就可以在安全有效的地方准确地改变所有权规则。使用智能指针,优化器永远无法将生成的代码恢复到我使用原始指针编写的代码的效率。不太熟练的程序员会发现获得收益的机会更少,并且冒更多错误的风险。
-
@JSF 我觉得那是个谬论。当然,在某些情况下您可以进行优化(例如,
std::unique_ptr目前不能传递到寄存器内的函数中,而原始指针可以)。但总的来说,很难做出这样的断言,因为编译器中的优化器变得越来越好,而且你在手动实现中做出的许多假设会因平台而异。特别是,我什至不确定您所说的“在 [放弃] 概念所有权之后使用……”是什么意思。这听起来像 UB。 -
独特的“概念”所有权意味着一次一个指针“拥有”该对象,程序员知道哪一个而编译器不知道。在移交概念所有权之后,您仍然有一个指向有效对象的指针,直到/除非新所有者将其删除。因此,在所有权被移交的那一刻和新所有者可以删除对象的稍后时间之间,原始指针可以有效访问该对象,而
std::unique_ptr则不会(所有假设都不需要异常安全)部分代码)。
标签: c++ pointers data-structures