在 c++ 中存储对象的最佳策略是什么,确保名称唯一并能够在以后有效地检索它们
What is the best strategy to store objects ensuring unique name and being able to efficiently retrieve them later, in c++?
我正在编写一个复合类(类似于Qt的QObject
),目前我将子级存储在std::vector
中。每个复合实例都有一个名称,并且此名称在作为该实例的同级的所有其他实例之间必须是唯一的,或者更好地说,在共享同一父实例的实例之间必须是唯一的。
每次将一个新实例推送到vector
内时,我都必须查看它的名称是否已被vector
中已有的实例之一使用,如果是,我必须更改其名称并添加一个数字。
我想出的代码非常愚蠢,当孩子的数量变得一致时,速度非常慢。
这是类:
class Composite
{
public:
Composite(const std::string &name, Composite *ancestor=NULL);
~Composite();
private:
std::string name;
Composite *ancestor;
std::vector<Composite*> descendants;
public:
void setName(const std::string &name);
};
这是构造函数和setName
实现:
Composite::Composite(const std::string &name, Composite *ancestor)
{
this->ancestor = ancestor;
setName(name);
if (ancestor!=NULL)
ancestor->descendants.push_back(this);
}
.
void Composite::setName(const std::string &name)
{
this->name = name;
if (ancestor!=NULL)
{
CompositeList::iterator dIt;
for( dIt=ancestor->descendants.begin(); dIt!=ancestor->descendants.end(); dIt++)
{
if ((*dIt)==this)
{
continue;
}
else if (this->name == (*dIt)->getName())
{
int trailingNumber = stringToInt(getTrailingNumber(this->name));
std::string cleanName = removeTrailingNumber(this->name);
this->name = cleanName+intToString(trailingNumber+1);
}
}
}
}
对于极少数孩子来说,这可能很好,但是当他们变成几百个孩子时,setName
功能真的变得很慢。想象一下这种情况:
Composite *parent = new Composite("pippo");
for (int i=0; i<10000; i++)
{
Composite("ClashingName", parent);
}
第一次没问题,第二次在冲突名称0中更改名称,第三次名称首先被卡入冲突名称0,然后找到与第二个实例的冲突并将名称设置为冲突名称1...你明白了,它是指数级的,当它到达那个循环结束时,会经过一个不可接受的时间。
所以这里真正的问题是如何有效地找到名称冲突并有效地分配一个尚未使用的新名称?任何 std 容器对我来说都很好,我的编译器支持 C++11,但我不能/不想使用 Boost,因为我正在处理的项目非常小(基本上是这个类)
我不是一个经验丰富的C++用户,我想使用map
或unordered_map
但我真的很渴望专家的建议。
IMO,您需要更改对象的存储方式。如下所示:
std::map<std::string, std::vector<Composite> >
映射的关键是前缀,向量中的索引是第 n 个Composite
对象。您需要提供一个自定义查找函数来拆分传入的名称。例如
std::string query = "pipo_10";
在您的查找函数中,
Composite* lookup(std::string const& key)
{
// split the key to get the prefix and the index
// lookup in the map using the prefix
// return the index
}
编辑1:要保存所有字符串操作,您可以定义自己的自定义键,它只是一对(例如,std::pair<std::string, int>
是前缀和索引),您的查找将仅使用此键中的值。
编辑2:多考虑一下,最好有一张地图,例如
std::map<std::string, std::map<int, Composite> >
现在,索引不再是向量中的索引,而是第二个映射中的查找。这样可以更好地处理删除,键将是我之前所说的组合键(对)。
归功于@Steves的建议..
std::map<std::pair<std::string, int>, Composite>
使用lower_bound()
技巧查找给定Composite
的最后一个索引
map
或unordered_map
将完成这项工作,使用 count
函数测试名称是否在地图中,并使用find
函数或operator[]
来访问它。
unordered_map
总体上可能会更快一些。
map
可以更好地处理您讨厌的"ClashingName"
示例,因为您可以使用lower_bound
或upper_bound
在单个查找中查找最后一个冲突的名称,而不是查找ClashingName0
中的每个名称...... 反过来ClashingName9999
。
请注意,默认情况下map
按字典顺序排序,因此ClashingName10
排在ClashingName9
之前。当有人为您提供包含数字的名称时会发生什么,尤其是在最后,也存在问题。
Nim 的建议解决此问题 - 使用一对string, int
作为映射键,并根据需要从该对构造名称。同样,当有人为您提供以数字结尾的名称时,您必须做一些特别的事情。确保名称"Clash10"
不能出现两次,一次是("Clash", 10)
,一次是("Clash1", 0)
。一个简单的选项是禁止提供的名称中的一个字符,并将其用作分隔符。
如果您不介意为每个对象添加额外的映射,则可以执行以下操作:
// inside Composite definiton
std::map<std::string, int> names_of_descendants;
然后简单地:
void Composite::setName(const std::string &name)
{
if (ancestor)
{
// for the first usage of certain name, map's operator[]
// will insert default constructed int (0) in the map
this->name = name + ancestor->names_of_descendants[name];
// increment the value for this name, for the next call of setName
++names_of_descendants[name];
}
}
您可以保留用于存储后代的向量。
- 何时在引用或唯一指针上使用移动语义
- C++优先级队列,按对象的唯一指针的特定方法升序排列
- 计算排序向量的向量中唯一值的计数
- 如何使用Visual Studio 2017在C++中为参数化对象数组使用唯一指针
- 使用VerQueryValue检索应用程序的文件描述
- 是否可以从格式字符串中检索"width"
- 通过组合不同的类型来创建唯一的id
- 使用 pqxx 将 std::vector 存储在 postgresql 中,并从数据库中检索它
- 使用Unique_ptr确保工厂中的对象唯一
- c++多进程编写一个唯一的文件
- 在以唯一ptr为值的C++映射中,动态内存何时会被销毁
- 如何更改唯一指针向量的可见性
- 在C++的两个字符串中连接以逗号分隔的唯一值
- 共享指针和具有自定义删除程序的唯一指针之间的语法差异背后的任何原因
- C++尝试深度复制唯一指针时出现内存访问冲突
- 具有引用成员的结构是否具有唯一的对象表示形式
- 使用 RTTI 克隆唯一指针的向量
- 如何在 c++ 中迭代数组中的唯一元素
- 为什么我无法创建唯一指针
- 在 c++ 中存储对象的最佳策略是什么,确保名称唯一并能够在以后有效地检索它们