智能指针与容器使用时的问题

from https://linkxzhou.github.io/post/stl%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E4%B8%8E%E5%AE%B9%E5%99%A8%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8%E4%BC%9A%E5%8F%91%E7%94%9F%E4%BB%80%E4%B9%88/#

智能指针简介

由于C++语言没有自动内存回收机制,程序员每次new出来的内存都要手动delete,为了有效缓解忘记内存delete,因此c++中auto_ptr、unique_ptr和shared_ptr这类将类封装成指针的模板对象,并在析构函数里编写delete语句删除指针指向的内存空间;对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存,所以智能指针都重载了“operator->”操作符,直接返回对象的引用,用以操作对象。那么先看几个栗子:

class SimpleTest 
{
public:
    SimpleTest(int test_id) : test_id_(test_id) 
    {}
    ~SimpleTest() {}
    void DoSomething()
    {
        std::cout << "DoSomething ... : " << test_id_ << std::endl;
    }
private:
    int test_id_;
};
void TestAutoPtr1()
{
    std::cout << "TestAutoPtr1 Test" << std::endl;
    std::auto_ptr<SimpleTest> my_ptr(new SimpleTest(1));
    if (my_ptr.get()) // 判断指针是否为空
    {
        my_ptr->DoSomething();
    }
}
void TestAutoPtr2()
{
    std::cout << "TestAutoPtr2 Test" << std::endl;
    std::auto_ptr<SimpleTest> my_ptr(new SimpleTest(2));
    if (my_ptr.get()) // 判断指针是否为空
    {
        std::auto_ptr<SimpleTest> my_ptr0; // 创建一个新的std::auto_ptr<SimpleTest>对象
        my_ptr0 = my_ptr; // 复制旧的 my_ptr 给 my_ptr0
        my_ptr0->DoSomething();
        my_ptr->DoSomething();
    }
}
void TestAutoPtr3()
{
    std::cout << "TestAutoPtr3 Test" << std::endl;
    std::shared_ptr<SimpleTest> my_ptr(new SimpleTest(3));
    if (my_ptr.get()) // 判断指针是否为空
    {
        std::shared_ptr<SimpleTest> my_ptr0; // 创建一个新的std::auto_ptr<SimpleTest>对象
        std::cout << "[1]UseCount: " << my_ptr.use_count() << std::endl;
        my_ptr0 = my_ptr; // 复制旧的 my_ptr 给 my_ptr0
        my_ptr0->DoSomething();
        my_ptr->DoSomething();
        std::cout << "[2]UseCount: " << my_ptr.use_count() << std::endl;
    }
}
void TestAutoPtr4()
{
    std::cout << "TestAutoPtr4 Test" << std::endl;
    std::unique_ptr<SimpleTest> my_ptr(new SimpleTest(4));
    if (my_ptr.get()) // 判断指针是否为空
    {
        std::unique_ptr<SimpleTest> my_ptr0; // 创建一个新的std::auto_ptr<SimpleTest>对象
        my_ptr0 = std::move(my_ptr); // 复制旧的 my_ptr 给 my_ptr0
        my_ptr0->DoSomething();
        my_ptr->DoSomething();
    }
}

那么问题来了:TestAutoPtr1、TestAutoPtr2、TestAutoPtr3、TestAutoPtr4这几个函数,那几个会出现crash呢?现在这里卖个关子,如果想了解智能指针童鞋在文章最后会给出相应的答案,下面先进入文章的问题:“stl智能指针与容器结合使用会发生什么”。

stl智能指针与容器结合使用会发生什么

先上一段代码:

void TestAutoPtr5()
{
    std::cout << "TestAutoPtr5 Test" << std::endl;
    std::vector< std::unique_ptr<SimpleTest> > vc;
    vc.push_back(std::unique_ptr<SimpleTest>(new SimpleTest(5)));
    vc.push_back(std::unique_ptr<SimpleTest>(new SimpleTest(6)));
    vc.push_back(std::unique_ptr<SimpleTest>(new SimpleTest(7)));

    // 1-----
    for (std::vector< std::unique_ptr<SimpleTest> >::iterator iter = vc.begin();
        iter != vc.end(); iter++)
    {
        (*iter)->DoSomething();
    }

    // 2-----
    vc.resize(5);
    for (std::vector< std::unique_ptr<SimpleTest> >::iterator iter = vc.begin();
    iter != vc.end(); iter++)
    {
        (*iter)->DoSomething();
    }
}

那么这段代码会发生什么?这段代码会发生crash,但是如果注释掉2下面那段代码,就没有问题。其实这段是由于resize引起的,vector对于空间不够的情况下,resize会扩展空间,那么同时会将对应的容器元素默认构造,那么std::unique_ptr默认构造会产生一个为NULL的指针,如果直接使用没有用get()判断那么就会core,但是如果vector的空间是够了(比如vc.resize(3)),resize不会产生新的元素,那么使用迭代器就不会有问题。
以上只是举了std::unique_ptr智能指针,使用其他的转移值也是一样,所以在使用容器的时候一定要注意容器是不是将元素里面的指针值转移或者智能指针有默认构造函数,将指针赋值为空(对于没有默认构造函数的智能指针不会有问题,因为一般在编译的时候就报错了,如:scoped_ptr)。

以此可看出智能指针尽量不要指向vector容器类型,因为当vector扩容时,智能指针便不再生效,引起程序的崩溃或未定义的行为。
=========================以上是我踩得坑,记录一下,方便大家翻阅=========================

回答第一节的问题:TestAutoPtr1不会core、TestAutoPtr2会core、TestAutoPtr3不会core、TestAutoPtr4会core,具体可以了解一下智能指针的指针转移过程。

引用

[1]http://blog.csdn.net/xt_xiaotian/article/details/5714477
[2]effective c++

原文地址:https://www.cnblogs.com/liumantang/p/12564151.html