C++智能指针使用说明

导读

STL提供四种智能指针:auto_ptr、unique_ptr、shared_ptr和weak_ptr。其中auto_ptr是C++98提供的解决方案,C++11以后均已摒弃。所有代码在gcc 8.1上编译。

设计思想

将基本类型指针封装为类对象指针模板,并在析构函数中编写delete语句删除指针指向的内存空间,并且每个智能指针类都有一个explicit构造函数。比如auto_ptr的类模板原型为:

template<typename _Tp>
class auto_ptr
{
private:
    _Tp* _M_ptr;      
public:   
   explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
   ~auto_ptr() { delete _M_ptr; }  
} 

auto_ptr

初始化方法:

1、构造函数拷

  • 将已经存在的指向动态内存的普通指针作为参数来构造。
int* p = new int(10);
auto_ptr<int> auto_p(p);
  • 直接构造。
auto_ptr<int> auto_p(new int(10));

2、拷贝构造

  • 利用已经存在的指针构造新的auto_ptr指针。因为动态内存只能由一个auto_ptr指针独享,所以在拷贝构造或赋值时会发生拥有权转移。在拷贝构造过程中,auto_p1失去对字符串内存的所有权,由auto_p2获得所有权。对象销毁时须由auto_p2负责内存的自动销毁。
auto_ptr< string > auto_p1 ( new string( "p1" ) );
auto_ptr< string > auto_p2( auto_p1 );

3、赋值

  •  利用已经存在的auto_ptr指针来构造新的auto_ptr指针。在赋值之前,由auto_p1指向的对象被销毁。赋值后auto_p1拥有int型对象所有权,值为20。auto_p2不在指向该对象(即成为空指针)。
auto_ptr< int > auto_p1( new int( 10 ) );
auto_ptr< int > auto_p2( new int( 20 ) );
auto_p1 = auto_p2;

注意事项:

  • 因为auto_ptr的所有权独有,所以防止两个auto_ptr对象指向同一块内存。这样会导致程序潜在的内存崩溃,这也是摒弃auto_ptr的原因。
#include <iostream>
#include <string>
#include <memory>

using namespace std;

int main() {
    auto_ptr<string> datas[3] =
    {
        auto_ptr<string>(new string("data1")),
        auto_ptr<string>(new string("data2")),
        auto_ptr<string>(new string("data3"))
    };
    auto_ptr<string> p;
    p=datas[1]; //datas[1]将所有权转给p,此时datas[1]不再指向"data2"字符串而变成空指针。
    for(int i=0; i<3; i++)
    {
        cout<<*datas[i]<<endl; //i=1时,程序崩溃,使用shared_ptr、unique_ptr可以避免程序本刊问题
    }
    cout<<*p<<endl;
    return 0;
}
  • 警惕auto_ptr智能指针作为参数。按值传递时,在函数调用过程中函数的作用域中会产生一个局部对象来接收传入的auto_ptr,此时传入的实参auto_ptr就失去了其对原对象的所有权,而该对象会在函数退出时被局部auto_ptr删除。按引用或指针传递时,不存在拷贝构造过程,但不能保证在函数内部对传入的auto_ptr做什么操作而产生auto_ptr指针被删除的问题。
void fun(auto_ptr<string> ap_)
{
    cout<<"print in fun:"<<*ap_<<endl;
}
int main() {
    auto_ptr<string> p (new string("data1"));
    fun(p);//函数执行完后,"data1"所在的对象被删除。
    cout<<*p<<endl;//引用空指针,程序崩溃。
    return 0;
}

 unique_ptr

unique_ptr是C++11提供的用于防止内存泄漏的智能指针,它独享被管理对象的所有权。unique_ptr对象封装原生指针,负责其生命周期。unique_ptr与auto_ptr相比,在编译环节就保证了unique_ptr的安全问题,因而比auto_ptr智能指针更安全。具体如下语句所示:

auto_ptr<string> p1(new string("data1"));
auto_ptr<string> p2;                        
p2 = p1;    //编译器认为合法,但后续对p1继续使用,程序运行时出错,因为p1不再指向有效数据。

unique_ptr<string> p3(new string("data2"));    
unique_ptr<string> p4;    
p4 = p3;    // 编译器认为非法,避免p3不再指向有效数据问题。

将unique_ptr指针赋给另一个unique_ptr指针时不会留下危险的悬挂指针。但当将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做。如果要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。

unique_ptr<string> demo(const char* s)
{
    unique_ptr<string> temp(new string(s));
    return temp;
}
int main() {

    /**
     * demo()返回临时指针时,p接管了原本归返回的unique_ptr所拥有的对象,而返回的unique_ptr被销毁,
     * 即unique_ptr没有机会访问无效数据。
     * */
    unique_ptr<string> p = demo("demo");
    cout<<*p<<endl;
    
    
    unique_ptr<string> p1(new string ("p1"));
    unique_ptr<string> p2;
    //p2 = p1;                                      // 编译器不允许
    unique_ptr<string> p3;
    p3 = unique_ptr<string>(new string ("3"));   // 编译器允许

    p2 = move(p); //p转让所有权,变成空指针。
    if(p2 != nullptr){
        cout<<*p2<<endl;
    }
    return 0;
}

shared_ptr

与unique_ptr独占所指向对象情况相反,shared_ptr基于"引用计数"模型实现,允许多个shared_ptr智能指针指向同一个动态对象,并维护一个共享引用计数器,当拷贝或赋值一个shared_ptr时计数器加一,被销毁时(如一个局部shared_ptr指针离开其作用域)计数器递减,当计数器变为0时,shared_ptr自动释放自己所管理的对象。

注意事项:

  • make_shared是最安全的指针初始化方式。
string s = "p";
shared_ptr<string> p = make_shared<string>(s);
  • 裸指针初始化多个share_ptr。
string s = "p";
shared_ptr<string> p = make_shared<string>(s);
/*不要这样做*/
shared_ptr<string> p1 = make_shared<string>(s);
cout<<*p<<endl;
cout<<*p1<<endl;

auto *p0 = new std::string("hello");
std::shared_ptr<std::string> p2(p0);
 /*不要这样做*/
std::shared_ptr<std::string> p3(p0);
cout<<*p2<<endl;
cout<<*p3<<endl;
  • 不用get()获取裸指针,然后去初始化另外一个shared_ptr,或者delete get返回的指针。
auto p = std::make_shared<std::string>("hi");
std::string *p0 = p.get();
std::shared_ptr<std::string> p2(p0);//非法
delete p0;//非法
  • 如果对象不是new分配的,传递删除器。
std::shared_ptr<T> make_shared_array(size_t size) {
    return std::shared_ptr<T>(new T[size], std::default_delete<T[]>());
} 
int main(){
     //lambda
    std::shared_ptr<int> p(new int[10], [](int* p){delete [] p;});
     //指定默认删除器
    std::shared_ptr<int> p1(new int[10], std::default_delete<int[]>());
    //自定义泛型方法 
    std::shared_ptr<char> p2 = make_shared_array<char>(10);
}
  • 循环引用导致内存泄漏。例如,假设我设计一个二叉树,并在其中包含一个指向左右子节点的指针。
class Node {
  int value;
public:
  shared_ptr<Node> leftPtr;
  shared_ptr<Node> rightPtr;
  Node(int val) : value(val) {
    cout << "Constructor" << endl;
  }
  ~Node() {
    cout << "Destructor" << endl;
  }
};

int main(){
    shared_ptr<Node> ptr = make_shared<Node>(4);
    ptr->leftPtr = make_shared<Node>(2);
    ptr->rightPtr = make_shared<Node>(5);
    cout<<ptr.use_count()<<endl;
    cout<<ptr->leftPtr.use_count()<<endl;
    cout<<ptr->rightPtr.use_count()<<endl;
    return 0;
}
/*
 运行正常,调用3次构造函数和3次析构函数,输出如下:
Constructor
Constructor
Constructor
1
1
1
Destructor
Destructor
Destructor
*/

如果给每个节点添加一个父节点时,则导致share_ptr内测泄漏。

class Node {
  int value;
public:
  shared_ptr<Node> leftPtr;
  shared_ptr<Node> rightPtr;
  shared_ptr<Node> parentPtr;
  Node(int val) : value(val) {
    cout << "Constructor" << endl;
  }
  ~Node() {
    cout << "Destructor" << endl;
  }
};

int main(){
    shared_ptr<Node> ptr = make_shared<Node>(4);
    ptr->leftPtr = std::make_shared<Node>(2);
    ptr->leftPtr->parentPtr = ptr;
    ptr->rightPtr = std::make_shared<Node>(5);
    ptr->rightPtr->parentPtr = ptr;
    cout<<ptr.use_count()<<endl;
    cout<<ptr->leftPtr.use_count()<<endl;
    cout<<ptr->rightPtr.use_count()<<endl;
    return 0;
}
/*
 运行非正常,调用3次构造函数,一直运行到程序结束也没调用析构函数,造成内存泄漏,输出如下:
Constructor
Constructor
Constructor
3
1
1
*/

weak_ptr

weak_ptr是一个伴随类,弱引用,weak_ptr允许共享,但不拥有对象,它的对象由shared_ptr创建。用于解决shared_ptr循环引用导致的内存泄漏问题。用法如下:

shared_ptr<int> ptr = make_shared<int>(4);
weak_ptr<int> weakPtr(ptr);

对于weak_ptr指针,不能直接用运算符*和->来访问关联的内存。必须先通过调用weak_ptr指针对象的lock函数来创建一个shared_ptr指针才能使用。

shared_ptr<int> ptr = make_shared<int>(4);
weak_ptr<int> weakPtr(ptr);
shared_ptr<int> ptr2 = weakPtr.lock();
if (ptr2)
{
   cout << (*ptr2) << endl;
}

使用weak_ptr改进二叉树示例,运行正常。

class Node {
  int value;
public:
  shared_ptr<Node> leftPtr;
  shared_ptr<Node> rightPtr;
  //把shared_ptr改为weak_ptr;
  weak_ptr<Node> parentPtr;
  Node(int val) : value(val) {
    cout << "Constructor" << endl;
  }
  ~Node() {
    cout << "Destructor" << endl;
  }
};
int main(){
    shared_ptr<Node> ptr = make_shared<Node>(4);
    ptr->leftPtr = std::make_shared<Node>(2);
    ptr->leftPtr->parentPtr = ptr;
    ptr->rightPtr = std::make_shared<Node>(5);
    ptr->rightPtr->parentPtr = ptr;
    cout<<ptr.use_count()<<endl;
    cout<<ptr->leftPtr.use_count()<<endl;
    cout<<ptr->rightPtr.use_count()<<endl;
}
/*
 运行正常,调用3次构造函数和3次析构函数,输出如下:
Constructor
Constructor
Constructor
1
1
1
Destructor
Destructor
Destructor
*/

 

posted @ 2021-03-11 16:56  钟齐峰  阅读(241)  评论(0编辑  收藏  举报