这是First Question的后续问题。
我有这堂课:
class Foo
{
std::map<char**, char*> values;
public:
~Foo() { cleanAll(); }
void cleanAll() {
for(auto& value : values)
{
delete[] value.second;
}
}
template<typename T> bool isChanged(T& entry)
{
char** addr = reinterpret_cast<char**>(&entry);
auto it = values.find(addr);
if(it == values.end())
{
char* oldBuf = new char[sizeof(T)];
T* oldEntry = new(oldBuf) T;
*oldEntry = entry;
values[addr] = oldBuf;
}
else
{
T* oldEntry = (reinterpret_cast<T*>(it->second));
if(entry != *oldEntry)
{
oldEntry->~T();
oldEntry = new(it->second) T;
*oldEntry = entry;
return true;
}
}
return false;
}
};
当我使用像aFoo.isChange(aInt)
这样的POD调用它时,aFoo
被破坏,一切正常。但是当它被调用类似aFoo.isChange(aString)
的复杂类型并且aFoo
被破坏时会出现内存泄漏。我假设这是因为析构函数不知道地图中char*
后面的什么,只是调用char
析构函数而不是它所指向的实际类型之一。
有没有办法摆脱这种内存泄漏而不改变地图values
?
答案 0 :(得分:1)
我知道您尝试通过将所有对象视为连续字节的简单序列来处理,以便监视值的某些更改。我不能判断它对于你打算做什么是一个好方法,但它显然是一种非常冒险的方法。
此代码失败并且是一个实时炸弹。
首先在cleanAll()
中delete[] value.second;
:这将使用new[]char
释放您自己分配的内存。但如果需要一个非平凡的析构函数(例如string
),它将不会安全地删除该对象!这意味着可能存在内存泄漏,还有其他可能无法正确释放的资源(例如文件,线程,互斥等等)。
接下来,在分配新元素时:*oldEntry = entry;
是T
个对象之间的分配。使用poitner的技巧可以使您使用适当的复制赋值运算符复制已分配的缓冲区。这意味着应用了对象副本的完整语义(例如,调整新内存,获取其他资源,或者更糟糕的是,更改原始对象。
现在关于指针的技巧:你在这里使用两个不同类型的指针来指代相同的内存位置。这是pointer aliasing并且由于编译器的假设不应该发生这种情况,可能会导致非常微妙的错误。
最后,当你在地图中重新分配一个exising元素时,你正确地使用了一个placement-new,首先直接调用析构函数oldEntry->~T();
,然后在旧元素的位置处创建对象。当然,这仅适用于旧对象和新对象的类型相同的情况。
结论:
在某些时刻,您将对象作为其内存布局的简单快照处理,并且在某些时刻,您可以使用具有完整语义和副作用的生物对象的完整尊严来对待它们。
您可以查看您的设计,例如使用boost::any
或boost::variant
来安全地管理包含不同类型对象的容器。
答案 1 :(得分:0)
感谢所有回复。我也不喜欢这种方法。在对问题进行了更多的工作后,我终于找到了一个很好的解决方案。我发布它作为我原来问题的答案。