C++感知复制插入到std::映射中

C++ Aware duplication insert into a std::map

本文关键字:映射 std 感知 复制 插入 C++      更新时间:2023-10-16

我有一个关于在C++中向std::映射中插入一些内容的问题。

这是我的代码:

字符串.hh:

unsigned long hashSDBM(char *strToHash){
unsigned char* str = new unsigned char[strlen(strToHash) + 1];
strncpy( (char *) str, strToHash, strlen(strToHash) );
unsigned long hash = 0;
int c;
while ((c = *str++)){
hash = c + (hash <<6) + (hash <<16) - hash;
}
return hash;
}

hashmap.hh

#include "stringutils.hh"
namespace{
using namespace std;
class MapElement{
private:
char* filename;
char* path;
public:
MapElement(char* f, char* p):filename(f), path(p){}
~MapElement(){
delete [] filename;
delete [] path;
}
char* getFileName(){ return filename; }
char* getPath(){ return path; }
};

class HashMap{
private:
map<long*, MapElement*> *hm;
long hash(char* key);
public:
HashMap(){
hm = new map<long*, MapElement*>();
}
~HashMap(){
delete hm;
}
long put(char* k, MapElement *v);
};
long HashMap::hash(char* key){
return stringutils::hashSDBM(key);
}

long HashMap::put(char* k, MapElement *v){
long *key = new long();
*key = hash(k);
pair<map<long*,MapElement*>::iterator, bool> ret;
ret = hm->insert(std::pair<long*, MapElement*>(key, v));
if(ret.second == false){
cerr<<"Already exists: "<<ret.first->second->getFileName()<<endl;
return *key;
}
cerr<<"INSERTED "<<*key<<endl;
return 0;
}

main.cc:

HashMap *hm = new HashMap();

int main(void){
MapElement *m1; 
char a[] = "hello";
char b[] = "world";
m1 = new MapElement(a,b);
hm->put(a, m1);
char c[] = "thats";
char d[] = "a test";
m1 = new MapElement(c,d);
hm->put(c, m1);
char e[] = "hello";
char f[] = "test";
m1 = new MapElement(e,f);
hm->put(e, m1);
return 0;
}

它的编译去掉了任何错误或警告,当我启动它时,会生成以下输出:

插入7416051667693574450

插入8269306963433084652

插入7416051667693574450

为什么第二个插入的"你好"没有任何效果?

std::map中的键是唯一的。如果要允许重复密钥,请使用std::multimap。您正在使用的map::insert返回一对迭代器和一个bool。bool表示插入是否已经实际插入(而不是键是否已经插入)。

为什么第二次插入键没有任何效果?

键是一个指针,指向具有相同值的不同long对象的两个指针是不同的键。如果不过度使用指针,你会对自己有很大帮助。C++不是Java。

好。。。在继续之前,请阅读一本好的C++书,C++标签描述中推荐了一些好的书。


所以,这里的问题是您的代码使用指针。。。处处指针的行为并不像你想象的那样。许多语言(如Java)都有普遍的引用类型:一切都只是引用。C++不是这样一种语言,它在指针/引用和值之间有很大的区别。

在您的特定情况下,long*是指向long的指针。就map而言,两个不同的指针只是:不同的,无论它们指向什么值。

所以。。。我们需要去掉那些指针。处处并且停止在C++中使用C习语。


字符串.hh

unsigned long hashSDBM(std::string const& strToHash){
unsigned long hash = 0;
for (char c: strToHash) {
hash = c + (hash <<6) + (hash <<16) - hash;
}
return hash;
}

简而言之:

  • 不要在C++中使用原始char*,内存所有权不明确,这会导致泄漏/悬挂指针
  • 适当使用const,一个不修改其参数的函数应该引用const
  • 使用C++11进行样式循环,它们与手动代码一样高效,同时更容易阅读,更难搞砸

hashmap.hh

namespace HashMap {
class MapElement{
public:
MapElement(std::string f, std::string p):
filename(f), path(p) {}
std::string const& getFileName() const { return filename; }
std::string const& getPath() const { return path; }
private:
std::string filename;
std::string path;
};

让我们从这里开始:

  • 头中没有匿名名称空间,它不会做你认为它会做的事情(读取它们)
  • 没有原始指针
  • 不要在商务课上浪费资源
  • const正确性很重要
  • 首先展示公共API,这是用户感兴趣的

继续:

class HashMap{
public:
unsigned long put(std::string const& k, MapElement v);
private:
static unsigned long hash(std::string const& key);
std::map<unsigned long, MapElement> hm;
};
inline unsigned long HashMap::hash(std::string const& key){
return stringutils::hashSDBM(key);
}
inline unsigned long HashMap::put(std::string const& k, MapElement v){
unsigned long const key = hash(k);
auto const ret = hm.emplace(key, v);
if (ret.second == false){
std:: cerr << "Already exists: " << ret.first->second.getFileName() << "n";
return key;
}
std::cerr << "INSERTED " << key << "n";
return 0;
}

好吧。。。

  • 不需要这么多指针,没有它们代码会更简单
  • 内部hash函数不访问任何状态,使其成为static
  • 在可能的最后时刻声明变量,并立即初始化它们。。。它允许您使用auto,而不是显式命名过于复杂的类型
  • std::endl不会做你认为它会做的事情(提示:它会刷新缓冲区!可能的I/O操作最慢!),只需使用普通的"n"

进一步说明:

  • 如果密钥必须是文件名,为什么要让用户提交密钥?您可以从MapElement对象中读取。。。或者在发生冲突时打印key(而不是文件名),以防它们不同
  • 哈希不是唯一的,如果两个不同的文件名哈希到相同的数字,你会拒绝第二个。。。您应该使用一个复合密钥(hash+filename)
  • 插入时返回0,不插入时返回key。。。但没有什么能阻止密钥是CCD_ 20,因此接收到CCD_

main.cpp

int main(void){
HashMap::HashMap hm;
hm.put("hello", MapElement("hello", "world"));
hm.put("thats", MapElement("thats", "a test"));
hm.put("hello", MapElement("hello", "test"));
return 0;
}

最后:

  • 避免全局
  • 无需命名所有临时人员