我正在观察std :: map :: clear()的奇怪行为。这个方法应该在调用时调用元素的析构函数,但是在调用clear()之后仍然可以访问内存。
例如:
struct A
{
~A() { x = 0; }
int x;
};
int main( void )
{
std::map< int, A * > my_map;
A *a = new A();
a->x = 5;
my_map.insert( std::make_pair< int, *A >( 0, a ) );
// addresses will be the same, will print 5
std::cout << a << " " << my_map[0] << " " << my_map[0]->x << std::endl;
my_map.clear();
// will be 0
std::cout << a->x << std::endl;
return 0;
}
问题是,为什么变量a
在map :: clear()调用析构函数后仍然可以访问?在致电delete a;
后我是否需要写my_map.clear()
或覆盖a
的内容是否安全?
先谢谢你的帮助, SNEG
答案 0 :(得分:22)
如果您将指针存储在地图(或列表或类似内容)上, YOU 负责删除指针,因为地图不知道它们是否已使用new创建, 或不。如果你不使用指针,clear函数只调用析构函数。
哦,还有一件事:调用析构函数(甚至调用delete)并不意味着无法再访问内存。它只意味着你将访问垃圾。
答案 1 :(得分:20)
std :: map不管理指针值指向的内存 - 由你自己完成。如果你不想使用智能指针,你可以写一个通用的免费&amp;明确这样的功能:
template <typename M> void FreeClear( M & amap )
for ( typename M::iterator it = amap.begin(); it != amap.end(); ++it ) {
delete it->second;
}
amap.clear();
}
并使用它:
std::map< int, A * > my_map;
// populate
FreeClear( my_map )
答案 2 :(得分:4)
这是因为map.clear()
调用地图中包含的数据的析构函数,在您的情况下,指向a
的指针。这没有任何作用。
您可能希望在地图中放置某种smart pointer,以便自动回收a
占用的内存。
BTW,为什么要将模板参数放入make_pair
的调用中?模板参数推导在这里应该做得很好。
答案 3 :(得分:1)
当你释放一块堆内存时,它的内容不会被归零。它们仅可再次分配。当然你应该考虑内存不可访问,因为访问未分配内存的影响是未定义的。
实际上阻止访问内存页面发生在较低级别,而std库不会这样做。
使用new分配内存时,除非使用智能指针,否则需要自行删除内存。
答案 4 :(得分:0)
任何容器都存储您的对象Type并调用相应的构造函数:每个节点的内部代码可能类似于:
__NodePtr
{
*next;
__Ty Val;
}
当你通过构造基于类型的val然后链接来分配它时。类似于:
_Ty _Val = _Ty();
_Myhead = _Buynode();
_Construct_n(_Count, _Val);
当你删除它时,调用相应的析构函数。
当你存储引用(指针)时,它不会调用任何构造函数,也不会破坏它。
答案 5 :(得分:0)
我花了最近两个月吃饭,睡觉和呼吸地图,我有一个建议。让地图尽可能地分配它自己的数据。由于你在这里强调的原因,它更清洁了。
还有一些微妙的优点,例如,如果您将数据从文件或套接字复制到地图的数据,则一旦节点存在,数据存储就会存在,因为当地图调用malloc()来分配节点时,它为密钥和数据分配内存。 (AKA map [key] .first and map [key] .second)
这允许你使用赋值运算符而不是memcpy(),并且需要少量调用malloc() - 你所做的那个。
IC_CDR CDR, *pThisCDRLeafData; // a large struct{}
while(1 == fread(CDR, sizeof(CDR), 1, fp)) {
if(feof(fp)) {
printf("\nfread() failure in %s at line %i", __FILE__, __LINE__);
}
cdrMap[CDR.iGUID] = CDR; // no need for a malloc() and memcpy() here
pThisCDRLeafData = &cdrMap[CDR.iGUID]; // pointer to tree node's data
需要注意的一些注意事项值得指出。
考虑一下“锚定”地图的位置。 IE:声明它们的位置。你不希望它们错误地超出范围。 main {}总是安全的。
INT _tmain(INT argc, char* argv[]) {
IC_CDR CDR, *pThisCDRLeafData=NULL;
CDR_MAP cdrMap;
CUST_MAP custMap;
KCI_MAP kciMap;
我非常幸运,我很高兴有一个关键地图分配一个结构作为它的节点数据,并让该结构“锚定”一个地图。虽然C ++已经抛弃了匿名结构(一个可怕的,可怕的决定必须颠倒),但是作为第一结构成员的地图就像匿名结构一样。非常光滑,干净,零尺寸效果。在函数调用中通过值传递指向叶拥有的结构或结构的副本,两者都可以很好地工作。强烈推荐。
为您节省大量的混淆和打字,并为您的地图使用typedef,就像这样。
typedef map<ULLNG, IC_CDR> CDR_MAP;
typedef map<ULLNG, pIC_CDR> CALL_MAP;
typedef struct {
CALL_MAP callMap;
ULNG Knt;
DBL BurnRateSec;
DBL DeciCents;
ULLNG tThen;
DBL OldKCIKey;
} CUST_SUM, *pCUST_SUM;
typedef map<ULNG,CUST_SUM> CUST_MAP, CUST_MAP;
typedef map<DBL,pCUST_SUM> KCI_MAP;
使用typedef和&amp;传递对地图的引用运算符在
中 ULNG DestroyCustomer_callMap(CUST_SUM Summary, CDR_MAP& cdrMap, KCI_MAP& kciMap)
对迭代器使用“auto”变量类型。编译器将从for()循环体的其余部分中指定的类型中找出要使用的map typedef类型。它太干净了,几乎是神奇的!
for(auto itr = Summary.callMap.begin(); itr!= Summary.callMap.end(); ++itr) {
定义一些清单常量,使得.erase和.empty()的返回更有意义。
if(ERASE_SUCCESSFUL == cdrMap.erase (itr->second->iGUID)) {
鉴于“智能指针”实际上只是保持引用计数,请记住,您可以始终保持自己的引用计数,可能是更清晰,更明显的方式。将它与上面的#5和#10相结合,你可以编写一些很好的干净代码。
#define Pear(x,y) std::make_pair(x,y) // some macro magic
auto res = pSumStruct->callMap.insert(Pear(pCDR->iGUID,pCDR));
if ( ! res.second ) {
pCDR->RefKnt=2;
} else {
pCDR->RefKnt=1;
pSumStruct->Knt += 1;
}
使用指针挂起到为自己分配所有东西的地图节点,IE:没有指向用户malloc()ed对象的用户指针,效果很好,可能更有效,并且可以用来变异根据我的经验,节点的数据没有副作用。
在同一主题上,这样的指针可以非常有效地用于保存节点的状态,如上面的pThisCDRLeafData
所示。将此传递给一个函数,该函数改变/更改特定节点的数据比传递对映射的引用更清晰,并且返回节点pThisCDRLeafData
所需的密钥指向。
迭代器不是魔术。它们既昂贵又缓慢,因为您在导航地图以获取值。对于包含百万个值的地图,您可以基于密钥以每秒约2000万个读取节点。使用迭代器,它可能慢约1000倍。
我认为现在就涵盖它。如果有任何变化或者有其他分享见解,将会更新。我特别喜欢使用带有C代码的STL。 IE:不是任何地方的视野。它们只是在我工作的环境中没有意义,而且这不是问题。祝好运。