Ich habe gelesen und experimentiert mit den Smart-Pointer der Standard-Bibliothek, unique_ptr
und shared_ptr
und obwohl sie offensichtlich große Ersatz für viele Fälle sind, in denen rohe Zeiger als gefährlich angesehen werden, bin ich unsicher bei der Verwendung von Datenstrukturen.Smart- oder Raw-Pointer beim Implementieren von Datenstrukturen?
Um zu experimentieren, habe ich ein Beispiel für eine Hash-Map geschrieben, die shared_ptr
verwendet - die nach Meyers Effective Modern C++ etwa doppelt so groß wie eine unique_ptr
sind. Aus diesem Grund würde ich gerne unique_ptr verwenden, aber ich bin ziemlich ratlos wegen dem, was ich in der Add-Funktion, Aktualisierung und Kopieren ausführe.
Hat jemand irgendwelche Vorschläge für dieses Problem? Sollen Datenstrukturen mit rohen Zeigern geschrieben werden?
#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;
}
Im Allgemeinen sollten Sie über die neuen Smart Pointer in Bezug auf * Besitz * nachdenken, statt einfach Zeiger automatisch zu löschen.Kann eine "Ressource" mehrere gleichzeitige Besitzer haben ('std :: shared_ptr') oder nur einen einzigen Benutzer gleichzeitig (' std :: unique_ptr'). –
Ich bin mir nicht sicher, warum Sie hier überhaupt Zeiger brauchen - oder 'std :: array'. Warum würden Sie die Hash-Map nicht in Form von 'std :: vector>' implementieren? Im Allgemeinen verwirrt mich die Frage ein wenig: das meiste, was Sie tun sollten, schreibt Datenstrukturen (und Algorithmen für sie). Folglich würden Sie * natürlich * intelligente Zeiger, nicht dumme Zeiger, für Datenstrukturen verwenden - wofür würden Sie dann sonst intelligente Zeiger verwenden? Es gibt nichts anderes. –
Beim Schreiben von Code wie einer Rotation in einer Baumdatenstruktur tendiere ich dazu, Mikrooptimierungen durchzuführen, was normalerweise die Verwendung von Zeigern ein paar Anweisungen einschließt, nachdem sie die konzeptionelle Eigentümerschaft aufgegeben haben. Solange ich rohe Zeiger benutze und die Besitz-Semantik des Codes verstehe, kann ich die Eigentumsregeln genau dorthin biegen, wo es sicher und effizient ist. Mit einem intelligenten Zeiger konnte der Optimierer den generierten Code nie so effizient zurückbekommen, wie ich es mit rohen Zeigern geschrieben hätte. Ein weniger erfahrener Programmierer würde weniger Möglichkeiten zum Nutzen finden und mehr Fehler riskieren. – JSF