2011-05-06 216 views
4

我遇到了Visual Studio 2010 C++编译器中的奇怪行为。 下面的代码编译但抛出 “调试断言失败” 执行后,用 消息:Visual Studio 2010 C++运行时错误

“_BLOCK_TYPE_IS_VALID(pHead-> nBlockUse)”

编译和下GCC顺利进行。这是我的错吗?

#include <iostream> 
#include <vector> 


using namespace std; 

typedef unsigned int uint; 


class Foo { 
    vector<int*> coll; 
public: 

    void add(int* item) { 
     coll.push_back(item); 
    } 

    ~Foo() { 
     for (uint i = 0; i < coll.size(); ++i) { 
      delete coll[i]; 
      coll[i] = NULL; 
     } 
    } 
}; 

int main() 
{ 
    Foo foo; 
    foo.add(new int(4)); 
    Foo bar = foo; 

    return 0; 
} 
+1

该错误是由VS调试运行时抛出;如果你编译它的版本,那么你不会得到这个错误,但是你会冒险崩溃。如果通过GCC,你的意思是Linux上的GCC,那么你可以通过在valgrind下运行你的代码来看到同样的错误。 – Rup 2011-05-06 15:57:25

回答

7

您没有实现复制构造函数和复制赋值运算符(请参阅三条规则)。这会导致向量中指针的浅拷贝,导致双重删除和断言。编辑:双删除是未定义的行为,所以VS和gcc在这里都是正确的,他们被允许做任何他们想要的。

通常,当你实现一个具有不平凡行为的析构函数时,你还需要编写或禁用复制构造和复制分配。

但在你的情况下,你是否真的需要通过指针存储项目?如果没有,只要按价值存储它们,那就能解决问题。否则,如果你确实需要使用指针shared_ptr(从你的编译器或升压),而不是原始指针,以节省您从需要编写自己的析构函数/复制方法。

编辑:有关您的界面的进一步说明:类似这样的接口传递指针传递的所有权可能会导致人们使用您的类的混淆。如果有人传入了未分配给堆的int地址,那么析构函数仍然会失败。更好的办法是,如果可能,请按价值接受,或者在add函数中将传入的项目复制到new

+0

我不会盲目推荐'shared_ptr',它们的行为很复杂(强/弱参考),因此在使用其他解决方案时最好避免它们。在这里,我可以看到'std :: vector ','std :: vector >'和'boost :: ptr_vector '作为优秀的竞争者。 – 2011-05-06 16:30:32

+0

感谢您的回答! – user742010 2011-05-06 16:35:46

3

你删除的项目两次,因为该行

Foo bar = foo; 

调用默认的拷贝构造函数,它复制了itempointer,而不是分配和复制数据。

2

问题是barfoo成员的向量元素是相同的。当foo超出范围时它的析构函数被调用这将释放指针离开bar向量元素晃来晃去。 bar析构函数试图释放它的这是左晃来晃去,并导致你的运行时错误向量元素。你应该写一个拷贝构造函数。

Foo bar = foo; // Invokes default copy constructor. 

编辑1:看看这些线了解Rule of three

0

这里的简单的解决方案是不是首先使用int*

#include <iostream> 
#include <vector> 


using namespace std; 

typedef unsigned int uint; 


class Foo { 
    vector<int> coll; // remove * 
public: 

    void add(int item) { // remove * 
     coll.push_back(item); 
    } 

    // remove ~Foo 
}; 

int main() 
{ 
    Foo foo; 
    foo.add(4); // remove `new` call 
    Foo bar = foo; 

    return 0; 
} 

一般情况下,尽量避免new

如果不能,使用智能管理器(比如std::unique_ptr)来处理内存清理你。

在任何情况下,如果你手动调用delete你就错了注:不调用delete,并让内存泄漏是错的太