【问题标题】:How to call a destructor with a casted pointer?如何使用强制转换的指针调用析构函数?
【发布时间】:2015-01-13 09:12:40
【问题描述】:

这是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的情况下摆脱这种内存泄漏?

【问题讨论】:

  • 这很容易惨败:char** addr = reinterpret_cast&lt;char**&gt;(&amp;entry);
  • 为什么?它只是用作地图的键。没有别的了。
  • 至少我什至不知道您为什么要尝试使用指针作为映射键。它们不会像您想象的那样在那里工作。
  • 人们使用演员表时的警钟。也使析构函数虚拟

标签: c++ templates memory-leaks destructor


【解决方案1】:

我了解您尝试通过将所有对象视为一个简单的连续字节序列来进行处理,以便监控值的一些变化。我无法判断这是否是您打算做的事情的好方法,但这显然是一种非常冒险的方法。

这段代码注定要失败,是一个实时炸弹。

首先在cleanAll()delete[] value.second;:这将释放你自己使用new[]char 分配的内存。但是如果需要非平凡的析构函数(例如string),它不会安全地删除对象!这意味着潜在的内存泄漏,以及其他可能无法正确释放的资源(例如文件、线程、互斥锁等)。

接下来,在分配新元素时:*oldEntry = entry;T 对象之间的赋值。 poitner 的技巧可以使您分配的缓冲区使用适当的复制赋值运算符进行复制。这意味着应用了对象副本的完整语义(例如,分配新内存、获取额外资源,或者更糟糕的是,更改原始对象。

现在关于指针的技巧:你在这里使用两个不同类型的指针来引用同一个内存位置。这是pointer aliasing,由于编译器假设这不应该发生,因此可能会导致非常微妙的错误。

最后,当您在地图中重新分配现有元素时,您正确地使用了新的位置,首先直接调用析构函数oldEntry-&gt;~T();,然后在旧元素的位置创建对象。当然,这只有在旧对象和新对象的类型相同时才有效。

结论:

在某些时候,您将对象视为其内存布局的简单快照,而在某些时候,您将它们视为具有完整语义和副作用的活对象的完整尊严。

您可以检查您的设计,例如使用boost::anyboost::variant 来安全地管理具有不同类型对象的容器。

【讨论】:

    【解决方案2】:

    感谢大家的回复。我也不喜欢这种方法。在解决这个问题后,我终于找到了一个好的解决方案。我将其发布为对我最初问题的回答。

    Solution

    【讨论】:

      猜你喜欢
      • 2012-01-10
      • 2018-03-05
      • 1970-01-01
      • 2012-12-11
      • 1970-01-01
      • 2014-01-13
      • 1970-01-01
      • 2014-03-13
      • 1970-01-01
      相关资源
      最近更新 更多