删除通过向上转型为基础指针的对象数组

4

我开始将一些库从msvc移植到mingw,在删除向上转型的对象数组时,发现了msvc非常有趣的行为。换言之,msvc进行了一些黑魔法(似乎喜欢这样做),下面的代码在msvc中可以正常执行,但在mingw(4.7.2)中会崩溃。我认为mingw表现正确,问题在于msvc使用了某种死板的代码导致了难以察觉的错误。

代码:

#include <iostream>

class foo{
    static int idgen;
protected:
    int id;
public:
    foo(){
        id = idgen++;
        std::cout << "Hello  ( foo - "<<id<<")"<<std::endl;
    }
    virtual ~foo(){
        std::cout << "Bye bye ( foo - "<<id<<")"<<std::endl;
    };
};

int foo::idgen = 0;


class bar: public foo{
    double some_data[20];
public:
    bar(){

    std::cout << "Hello  ( bar - "<<id<<")"<<std::endl;
}
    ~bar(){
        std::cout << "Bye bye ( bar - "<<id<<")"<<std::endl;
    }
};

int main()
{
    const unsigned int size = 2;
    foo** arr = new foo*[size];
    {
        bar* tmp = new bar[size];
        for(int i=0; i<size; i++)
        {
            arr[i] = &(tmp[i]); //take address of each object
        }
    }

    delete [] arr[0]; //take address of first object, pointer is same as tmp. This also crashes mingw
    delete [] arr;

}

来自msvc 2010的输出

Hello  ( foo - 0)
Hello  ( bar - 0)
Hello  ( foo - 1)
Hello  ( bar - 1)
Bye bye ( bar - 1)
Bye bye ( foo - 1)
Bye bye ( bar - 0)
Bye bye ( foo - 0)

并且 mingw(在销毁时崩溃)
Hello  ( foo - 0)
Hello  ( bar - 0)
Hello  ( foo - 1)
Hello  ( bar - 1) 

我的问题是,修复这个问题的正确方法是什么。我目前想到的hackfix只涉及尝试向每个可能的类进行向下转换,并在向下转换后的指针上调用删除操作:

if(dynamic_cast<bar*>(arr[0]) != 0)
    delete [] dynamic_cast<bar*>(arr[0]); 

除了重新设计库(它不是我的),还有更好的方法吗?


哎呀,我应该把这个眼罩摘下来 :-/ - user1898811
1
我试图弄清楚为什么这在MSVS上甚至能工作,看起来你在这里使用了一个奇怪的黑客技巧:&(tmp[0])返回与tmp相同的值,因此当您调用delete[] arr [0]时,您一次性删除整个tmp数组。 - Sergey Kalinichenko
1
@dasblinkenlight 现在你明白了 ;) 这在 msvc 中能够工作真的很奇怪,但这是该库最初开发的地方,导致这成为一个根深蒂固的问题。 - Honf
@Honf 尝试将 arr[i] = &(tmp[i]); 替换为 arr[i] = (!i) ? tmp : &(tmp[i]);,看看是否会有任何变化(尽管我怀疑会不会有变化)... - Sergey Kalinichenko
1
@dasblinkenlight arr[i] = &(tmp[i]);arr[i] = (!i) ? tmp : &(tmp[i]); 是等价的,因为它们取相同的地址。我怀疑问题出在 new[]delete[] 所使用的元数据上,因为标准中没有对此进行定义,导致了这种依赖实现的边界情况。对我来说,最大的问题是,哪种行为是正确的,或者删除向上转型的对象数组是否是未定义的操作? - Honf
显示剩余3条评论
2个回答

4
在标准规范中,第5.3.5节第3段关于delete运算符的说明如下:

[...] 在第二种情况(delete array)中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义。

因此,在这种情况下,您不应该依赖Visual C++的温和行为,并尝试为数组delete运算符提供正确类型的指针,这基本上意味着在您的情况下进行动态转换。
您可以通过使用向量实例来存储逐个分配的向上转型对象来避免该问题。

1

即使是一个简单的例子,gcc 4.7.2也会失败 -> ideone.com/z876QX#view_edit_box。因此,如果它是一个数组,我们似乎无法使用虚析构函数。

const unsigned int size = 2;
foo* test = new bar[size];
delete[] test;

然而,如果您使用指针数组,就可以使用delete而不是delete[]来释放内存。

http://ideone.com/NfbF3n#view_edit_box

const int size = 5;
foo *test[size];
for(int i = 0; i < size; ++i)
    test[i] = new bar;
for(int i = 0; i < size; ++i)
   delete test[i];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接