【问题标题】:Does deleting void pointer guarantee to delete right size? [duplicate]删除 void 指针是否保证删除正确的大小? [复制]
【发布时间】:2012-09-27 12:46:10
【问题描述】:

可能重复:
Is it safe to delete a void pointer?

假设我有一个 new 分配给一个名为 MyClass 的类,分配很简单:

MyClass *myClassPtr = new MyClass();

我存储对 void* 列表的引用,我只是说

myListOfPointers.add(static_cast<void*>(myClassPtr)); // this has to be void*

后来我释放内存而不是这样做:

delete myClassPtr

我用:

delete MyListOfPointer.get(0)

(假设 myClassPtr 引用位于零索引处。)另外,请注意它必须是 void*,因为此列表可以存储不同类型的指针,所以我不知道我要删除的指针类型:

所以我不能这样做:

delete static_cast<MyClass*>(MyListOfPointer.get(0))

这种方式会释放正确的内存大小吗? (sizeof(MyClass))?

注意:
我不是在寻找任何指向智能指针的答案。

【问题讨论】:

  • 您还会遇到未调用析构函数的潜在问题...希望您意识到这一点...
  • 是的,我当然知道,但在这种情况下,我主要关心的是内存释放大小
  • 这看起来类似于stackoverflow.com/questions/941832/…(答案是删除空指针不安全)

标签: c++ pointers memory-management void


【解决方案1】:

通过void* 删除会导致未定义的行为,因此您不会得到任何保证。

5.3.5 删除[expr.delete]

1 delete-expression 运算符销毁由 new-expression 创建的最派生对象 (1.8) 或数组。
[...]
操作数应具有指向对象类型的指针,或具有指向对象类型指针的单个非显式转换函数 (12.3.2) 的类类型。结果的类型为 void。78

78) 这意味着不能使用 void* 类型的指针删除对象,因为 void 不是对象类型。

强调我的。


所以即使你说不说,答案是创建某种形式的智能指针。它需要使用类型擦除在外部隐藏类型(允许异构列表),但在内部跟踪它给出的类型以及如何删除它。很像boost::any

【讨论】:

  • 除了很多不确定性 =)
  • 任何您认为存储类型信息以便以后检索的方式。结构类似于:myClassPtr - MyClass myText - String .... 等
  • 这不是未定义的行为,它的格式不正确。 void* 不是指向对象的类型,也不是带有转换运算符的类类型,因此不符合条件。
  • @Xeo:我认为其他地方的共识是打破“shall”是未定义的行为,如果文本包含“,则它的格式不正确,否则程序格式错误。”无论哪种方式,我都不反对这种解释(这就是我所期望的)。
  • @mkhan3189:视情况而定。正如我在答案中添加的那样,boost::any 朝着纯和基本类型擦除的正确方向前进。否则,听起来您的设计需要更改。您的所有对象都可以从具有虚拟析构函数的基类派生吗?然后将std::unique_ptr 存储到派生类的实例中。如果不是,类型擦除实际上就是抽象的一层。
【解决方案2】:

void 指针没有类型信息。如果MyClass 有析构函数,则不会调用它。编译器需要知道它正在删除什么,以便生成适当的代码。如果列表中的所有指针都属于同一类型,那么您应该将该类型存储在列表中,而不是void。如果指针是不同类型但派生自公共基类型,则为该基类型提供一个虚拟构造函数并存储该类型的指针。

【讨论】:

  • 我以前想过这个(有一个通用的基类),但我认为我的设计有问题,我相信有这样的设计会是个好主意。这篇文章很有帮助,所以 +1 :)
  • 你的想法是什么:如果我有一个公共基类(wit virtual cont'r 和 dest'r)并通过类型转换删除该基类static_cast&lt;BaseClass*&gt;(myClassPointer) 会删除正确的大小吗? (显然 dest'r 问题将得到解决,因为它具有虚拟析构函数)但我关心内存大小。
  • 当然你的意思是“虚拟析构函数”。没有“虚拟构造函数”这样的东西。
  • @mkhan3189:如果你有一个带有虚拟析构函数的通用基类,delete 可以正常工作。这就是拥有虚拟析构函数的全部意义所在。 (此外,static_cast&lt;BaseClass*&gt; 是不必要的。指向派生类型的指针可以隐式转换为指向基类的指针。)
【解决方案3】:

没有必要使用智能指针,它只是智能的。

话虽如此,还有很多其他的可能性;唯一要做的就是将类型信息存储在实际对象中。

class Holder {
public:
    template <typename T>
    explicit Holder(T const volatile* t):
        _data(static_cast<void const volatile*>(t)),
        _delete(&Delete<T>)
    {}

    void apply() { _deleter(_data); }

private:
    typedef void (*Deleter)(void const volatile*);

    template <typename T>
    static void Delete(void const volatile* p) {
        delete static_cast<T const volatile*>(p);
    }

    void const volatile* _data;
    Deleter _deleter;
};

现在:

std::list<Holder> owningList;

owningList.push_back(Holder(somePointer));

for (Holder& h: owningList) { h.apply(); }

【讨论】:

  • @Matthew,哦,不,抱歉,兄弟并没有粗鲁的意思,但你的例子根本没有帮助,因为我明确表示我的指针将是不同的类型。我收回我的话,但并没有粗鲁的意思。兄弟加油,
  • @mkhan3189:很抱歉当时很敏锐。如果您仔细观察,您会注意到该类的构造函数是模板化的。因此,您可以将它用于不同的类型,只要在构建Holder 类之前保留类型信息即可。使用在其析构函数中打印的对象进行测试,您会感到惊讶。
  • @Mathieu Bro,我不需要为了感到惊讶而对其进行测试,我只是看着它就已经感到惊讶了。我不是在说这个类是一个模板。我说的是 std::list 正在存储的对象类型。它不应该只绑定到Holder 类,它应该是一个generic pointer holder,它要么是void*,要么是许多派生类的公共基类。所以,所有的好兄弟,你的例子还不错,但肯定不是最聪明的。我仍然想知道你为什么把它弄得这么复杂,但那是你的选择。非常感谢您的回答
  • @mkhan3189:这实际上就是我在谈到类型擦除时所说的......仅仅因为某些事情对来说似乎很复杂并不意味着它过于复杂或不是最聪明的。这是一种非常标准的方法(尽管我个人会在这里或那里进行一些调整)。
  • @mkhan3189: 很高兴看到你终于做到了 :) 它看起来确实有点像魔术,但是当你意识到虚方法只不过是动态调用的函数指针时,你也意识到你可以无需继承即可完美获得这种“动态”行为。将模板加入组合中以获得自动类型推导自动函数创建,您将获得一个神奇的组合:)
【解决方案4】:

这个问题的正确答案当然是“不”

编辑:要求提供更多信息,即使我已经在对该问题的评论中做了,删除 void* 是未定义的,这个问题是另一种提问方式:Is it safe to delete a void pointer? - 查看答案在那里了解详细信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-24
    • 2020-06-19
    • 2017-09-10
    • 1970-01-01
    • 2015-11-27
    • 2013-12-23
    • 2017-10-08
    相关资源
    最近更新 更多