智能指针 shared_ptr 的使用方法

基于Boost库, C++11 加入了shared_ptrweak_ptr. 它们最早在TR1中就被引入, 但在C++11中, 在Boost的基础上又加入了新的功能.

std::shared_ptr使用引用计数. 每一个shared_ptr的拷贝都指向相同的内存. 在最后一个shared_ptr析构的时候, 内存才会被释放.

 std::shared_ptr<int> p1(new int(5));
std::shared_ptr<int> p2 = p1; // 都指向同一内存.
 
p1.reset(); // 因为p2还在,所以内存没有释放.
p2.reset(); // 释放内存, 因为没有shared_ptr指向那块内存了.

std::shared_ptr 使用引用计数, 所以有循环计数的问题. 为了打破循环,可以使用std::weak_ptr. 顾名思义, weak_ptr是一个弱引用, 只引用, 不计数. 如果一块内存被shared_ptr和weak_ptr同时引用, 当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存, 内存也会被释放. 所以weak_ptr不保证它指向的内存一定是有效的, 在使用之前需要检查.

std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; // 还是只有p1有所有权.
 
{
  std::shared_ptr<int> p2 = wp1.lock(); // p1和p2都有所有权
  if(p2) // 使用前需要检查
  { 
    // 使用p2
  }
} // p2析构了, 现在只有p1有所有权.
 
p1.reset(); // 内存被释放.
 
std::shared_ptr<int> p3 = wp1.lock(); // 因为内存已经被释放了, 所以得到的是空指针.
if(p3)
{
  // 不会执行到这.
}

shared_ptr的成员方法,user_count()的作用是获得当前对象被引用的次数,reset()的作用是释放指针对对象的引用,将指针设为空。
shared_ptr的线程安全性:
它是这样说的:

Boost 文档对于 shared_ptr 的线程安全有一段专门的记述,内容如下:
shared_ptr objects offer the same level of thread safety as built-in types. A shared_ptr instance can be "read" (accessed using only const operations) simultaneously by multiple threads. Different shared_ptr instances can be "written to" (accessed using mutable operations such as operator= or reset) simultaneosly by multiple threads (even when these instances are copies, and share the same reference count underneath.)
Any other simultaneous accesses result in undefined behavior.
翻译为中文如下:
shared_ptr 对象提供与内建类型一样的线程安全级别。一个 shared_ptr 实例可以同时被多个线程“读”(仅使用不变操作进行访问)。 不同的 shared_ptr 实例可以同时被多个线程“写入”(使用类似 operator= 或 reset 这样的可变操作进行访问)(即使这些实 例是拷贝,而且共享下层的引用计数)。
任何其它的同时访问的结果会导致未定义行为。”

这几句话比较繁琐,我总结一下它的意思:

1 同一个shared_ptr被多个线程“读”是安全的。

2 同一个shared_ptr被多个线程“写”是不安全的。

3 共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的。

如何印证上面的观点呢?

其实第一点我觉得比较多余。因为在多个线程中读同一个对象,在正常情况下不会有什么问题。

所以问题就是:如何写程序证明同一个shared_ptr被多个线程"写"是不安全的?

我的思路是,在多个线程中同时对一个shared_ptr循环执行两遍swap。 shared_ptr的swap函数的作用就是和另外一个shared_ptr交换引用对象和引用计数,是写操作。执行两遍swap之后, shared_ptr引用的对象的值应该不变。

程序如下:

#include <stdio.h>
#include <tr1/memory>
#include <pthread.h>

using std::tr1::shared_ptr;

shared_ptr<int> gp(new int(2000));

shared_ptr<int>  CostaSwapSharedPtr1(shared_ptr<int> & p)
{
    shared_ptr<int> p1(p);
    shared_ptr<int> p2(new int(1000));
    p1.swap(p2);
    p2.swap(p1);
    return p1;
}

shared_ptr<int>  CostaSwapSharedPtr2(shared_ptr<int> & p)
{
    shared_ptr<int> p2(new int(1000));
    p.swap(p2);
    p2.swap(p);
    return p;
}


void* thread_start(void * arg)
{
    int i =0;
    for(;i<100000;i++)
    {
        shared_ptr<int> p= CostaSwapSharedPtr2(gp);
        if(*p!=2000)
        {
            printf("Thread error. *gp=%d \n", *gp);
            break;
        }
    }
    printf("Thread quit \n");
    return 0;
}



int main()
{
    pthread_t thread;
    int thread_num = 10, i=0;
    pthread_t* threads = new pthread_t[thread_num];
    for(;i<thread_num;i++)
        pthread_create(&threads[i], 0 , thread_start , &i);
    for(i=0;i<thread_num;i++)
        pthread_join(threads[i],0);
    delete[] threads;
    return 0;
}

这个程序中我启了10个线程。每个线程调用10万次 CostaSwapSharedPtr2函数。 在CostaSwapSharePtr2函数中,对同一个share_ptr全局变量gp进行两次swap(写操作), 在函数返回之后检查gp的值是否被修改。如果gp值被修改,则证明多线程对同一个share_ptr执行写操作是不安全的。

程序运行的结果如下:


Thread error. *gp=1000 
Thread error. *gp=1000 
Thread quit 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread error. *gp=1000 
Thread quit 
Thread quit

10个线程有9个出错。证明多线程对同一个share_ptr执行写操作是不安全的。
我们在程序中,如果不运行CostaSwapSharedPtr2, 改成运行CostaSwapSharedPtr1呢?
  CostaSwapSharedPtr1和CostaSwapSharedPtr2的区别在于, 它不是直接对全局变量gp进行写操作,而是将gp拷贝出来一份再进行写操作。运行的结果如下:
costa@pepsi:~/test/cpp/shared_ptr$ ./b
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit 
Thread quit


你可能感兴趣的:(C++)