实现数据结构时的智能或原始指针?

时间:2016-01-07 16:41:29

标签: c++ pointers data-structures

我一直在阅读和试验标准库的智能指针unique_ptrshared_ptr,虽然它们显然是很多可能被认为是危险的原始指针的替代品我不确定它们在实现数据结构时的用途。

为了进行实验,我编写了一个使用shared_ptr的哈希映射示例 - 根据Meyer的Effective Modern C ++,它大约是unique_ptr的两倍。出于这个原因,我想使用unique_ptr,但由于我在添加功能,更新和复制中所执行的操作,我有点难过。

有没有人对此问题有任何建议?数据结构是否应该使用原始指针写入?

#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;
}

1 个答案:

答案 0 :(得分:9)

智能指针的选择取决于您的数据结构&#34;拥有&#34;堆分配的对象

如果您只需要观察,而不是拥有对象(无论是否为堆分配) raw 指针,引用std::reference_wrapper是合适的选择。

如果您需要唯一所有权(最多一个堆分配对象的所有者),请使用std::unique_ptr。它没有额外的时间/内存开销。

如果您需要共享所有权(堆分配对象的任意数量的所有者),请使用std::shared_ptr。这会导致额外的时间/内存开销,因为必须存储额外的指针(到引用计数元数据),并且因为访问它所保证是线程安全的。

除非您确实需要拥有对象,否则无需使用std::unique_ptr (代替原始指针)

假设您需要拥有该对象,除非您确实需要,否则无需使用std::shared_ptr (代替std::unique_ptr共享所有权语义

在您的情况下,您的HashMap中最多有一个堆节点。因此,我假设您希望 HashMap实例成为节点的唯一所有者

您应该使用什么类型?

template<typename K, typename T, typename F = HashKey<K>>
class HashMap {
public:
    std::array</* ? */, 128 > m_table;
    // ...
};

您有两种选择:

  1. 如果要使用堆间接存储对象,请使用std::unique_ptr,因为这些堆分配对象的唯一所有者始终是{{ 1}}实例。

  2. 如果要将对象直接存储到HashMap中,没有堆间接,那么根本不要使用任何指针。这可能导致非常大的HashMap个实例。用于访问HashMap节点的接口变得很麻烦。

  3. 选项1(在堆中存储节点):

    这是最常见,也可能是最佳选择。

    next

    这将导致更轻的(就内存占用而言) template<typename K, typename T, typename F = HashKey<K>> class HashMap { public: std::array<std::unique_ptr<HashNode<K, T>>, 128 > m_table; // ... }; 实例。

    注意:使用HashMap代替std::vector会显着减少std::array的大小,但会引入额外的堆间接。 这是实现类似数据结构的常用方法。您通常希望HashMap实例尽可能轻量级,以便可以有效地复制/移动/存储它。

    由于节点完全由HashMap拥有,因此不需要使用智能指针来连接彼此之间的节点。 原始指针就足够了

    HashMap

    上述代码可以正常工作,假设访问template<typename K, typename T> class HashNode { public: // ... HashNode* next_ptr = nullptr; auto& next() { assert(next_ptr != nullptr); return *next_ptr; } }; HashMap仍然有效。

    选项2(在地图实例中存储节点):

    next

    template<typename K, typename T, typename F = HashKey<K>> class HashMap { public: std::array<HashNode<K, T>, 128 > m_table; // ... }; 个实例可能非常庞大,具体取决于HashMap的大小。

    如果您选择将节点直接存储到没有堆间接的HashNode<K, T>中,则必须使用索引来访问内部数组,因为移动/复制HashMap将改变节点的内存地址。

    HashMap