智能指针就是帮我们C++程序员管理动态分配(程序员手动创建,手动释放,也就是说智能指针是帮我们操作堆区内存的)的内存的,它会帮助我们自动释放new出来的内存,从而避免内存泄漏!智能指针既然会自动delete对象,我们就不能再去手动delete对象了,否则,也会发生多次释放的问题。

auto_ptr

auto_ptr已经被unique_str代替

auto_ptr和unique_str类似,但是允许左赋值操作,这样会带来风险

 1 vector<auto_ptr<string>> vec;
 2 auto_ptr<string> p3(new string("I'm P3"));
 3 auto_ptr<string> p4(new string("I'm P4"));
 4 
 5 // 必须使用std::move修饰成右值,才可以进行插入容器中
 6 vec.push_back(std::move(p3));
 7 vec.push_back(std::move(p4));
 8 
 9 cout << "vec.at(0):" <<  *vec.at(0) << endl;
10 cout << "vec[1]:" <<  *vec[1] << endl;
11 
12 
13 // 风险来了:
14 vec[0] = vec[1];    // 如果进行赋值,问题又回到了上面一个问题中。
15 cout << "vec.at(0):" << *vec.at(0) << endl;
16 cout << "vec[1]:" << *vec[1] << endl;

auto_ptr不支持对象数组内存管理

1 auto_ptr<int[]> array(new int[5]);    // 不能这样定义

unique_ptr

特性

  1. 基于排他所有权模式:两个指针不能指向同一个资源
  2. 无法进行左值unique_ptr赋值构造,也无法进行左值赋值操作,但允许临时右值赋值构造和赋值
  3. 当unique_ptr指针被销毁时自动释放它指向的对象
  4. 在容器中保存指针是安全的

创建,初始化

1 unique_ptr<int>up;
2 up = make_unique<int>(100);
3 cout << *up << endl;

对2进行说明

 1 unique_ptr<string> p1(new string("I'm Li Ming!"));
 2 unique_ptr<string> p2(new string("I'm age 22."));
 3     
 4 cout << "p1:" << p1.get() << endl;
 5 cout << "p2:" << p2.get() << endl;
 6 
 7 p1 = p2;                    // 禁止左值赋值
 8 unique_ptr<string> p3(p2);    // 禁止左值赋值构造
 9 //个人认为最好不要进行下面的操作,因为它和使用auto_ptr进行左值赋值操作就一样了
10 unique_ptr<string> p3(std::move(p1));
11 p1 = std::move(p2);    // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样
12 
13 cout << "p1 = p2 赋值后:" << endl;
14 cout << "p1:" << p1.get() << endl;
15 cout << "p2:" << p2.get() << endl;

p2赋值给别人后就指向null了,因为不允许两个unique_str指向同一个块内存

对4的说明

 1 vector<unique_ptr<string>> vec;
 2 unique_ptr<string> p3(new string("I'm P3"));
 3 unique_ptr<string> p4(new string("I'm P4"));
 4 
 5 vec.push_back(std::move(p3));
 6 vec.push_back(std::move(p4));
 7 
 8 cout << "vec.at(0):" << *vec.at(0) << endl;
 9 cout << "vec[1]:" << *vec[1] << endl;
10 
11 vec[0] = vec[1];    /* 不允许直接赋值 */通过容器直接赋值编译器会报错,报错就说明是安全的
12 vec[0] = std::move(vec[1]);        // 需要使用move修饰,使得程序员知道后果

支持数组的内存管理

1 unique_str<int[]>array(new int[5]);

智能指针常用的三种函数(这里以unique_str作为说明)

get()

将智能指针指向的内存地址赋值给裸指针,智能指针仍然指向其原来所指的地址。注意裸指针要手动释放掉堆区内存。

1 unique_ptr<int>up;
2 up = make_unique<int>(100);
3 int* p;
4 //p = up;//不能这样做
5 p = up.get();
6 cout << "p:" << p << endl;
7 cout << "unique:" << up << endl;

release()(只有unique_str有)

将智能指针所指的内存地址赋给裸指针,智能指针将不在指向它原来的地址,智能指针指向空。注意裸指针要手动释放掉堆区内存。

1 unique_ptr<int>up;
2 up = make_unique<int>(100);
3 int* p;
4 p = up.release();
5 cout << "p:" << p << endl;
6 cout << "unique:" << up << endl;

reset()

unique_str使用该函数则释放智能指针指向的内存(因为unique_str独占这块内存),如果不在括号中指定新的地址(等价于直接将智能指针赋值为空)智能指针将指向空。

shared_ptr使用该函数则不会释放智能指针指向的内存,如果不在括号中指定新的地址(等价于直接将智能指针赋值为空)智能指针将指向空。

1 unique_ptr<int>up;
2 up = make_unique<int>(100);

4 up.reset();
5 cout << "unique:" << up << endl;

1 unique_ptr<int>up;
2 up = make_unique<int>(100);
3 
4 up.reset(new int(100));
5 cout << "unique:" << up << endl;

shared_str

如果有一种方式,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它!这就是 shared_ptr 采用的策略!

use_count()

引用计数,判断有几个指针指向该内存空间。

share_str可以左值赋值,也可以左值赋值构造,赋值后引用计数加1。

循环引用

不要对shared_ptr使用循环引用

循环引用的含义:

A类的成员属性有一个B类类型的shared_ptr,B类的成员属性有一个A类类型的shared_ptr。创建一个A类类型的堆区内存,创建一个B类类型的堆区内存,A类类型的shared_ptr指向B类类型的堆区内存,B类类型的shared_ptr指向A类类型的堆区内存。注意必须两种类型的实例互指。这样就无法析构掉A,B两种类型的堆区内存,造成内存泄漏。造成这个问题的核心是程序在销毁任何东西时必须有先有后,也就是说A类类型的shared_ptr指向B类类型shared_ptr不能同时销毁。

循环引用的实例

 1 class Girl;
 2 class Boy {
 3 public:
 4     Boy() {
 5         cout << "Boy的构造" << endl;
 6     }
 7     void setgirl(shared_ptr<Girl>girlfriend) {
 8         this->_girlfriend = girlfriend;
 9     }
10     ~Boy() {
11         cout << "~Boy 析构函数" << endl;
12     }
13 private:
14     shared_ptr<Girl>_girlfriend;
15 };
16 class Girl {
17 public:
18     Girl() {
19         cout << "Girl的构造" << endl;
20     }
21     void setBoy(shared_ptr<Boy>boyfriend) {
22         this->_boyfriend = boyfriend;
23     }
24     ~Girl() {
25         cout << "~Girl 析构函数" << endl;
26     }
27 private:
28     shared_ptr<Boy>_boyfriend;
29 };
int main() {
    shared_ptr<Boy>spboy(new Boy);//Boy的堆区内存被spboy和Girl的共享指针类型的成员属性同时指向
    shared_ptr<Girl>spgirl(new Girl);//Girl的堆区内存被spgirl和Boy的共享指针类型的成员属性同时指向

    spboy->setgirl(spgirl);
    spgirl->setBoy(spboy);

    system("pause");
    return 0;
}

以下程序没有构成循环引用:

 1 int main() {
 2     shared_ptr<Boy>spboy(new Boy);//Boy的堆区内存被spboy和Girl的共享指针类型的成员属性同时指向
 3     shared_ptr<Girl>spgirl(new Girl);//Girl的堆区内存被spgirl和Boy的共享指针类型的成员属性同时指向
 4     shared_ptr<Girl>spgirlgirl(new Girl);//Girl的堆区内存被spgirl和Boy的共享指针类型的成员属性同时指向
 5     spboy->setgirl(spgirlgirl);
 6     spgirl->setBoy(spboy);
 7     
 8     system("pause");
 9     return 0;
10 }

造成上面的原因如下:

个人认为spboy和spgirl存放在栈区,根据先进后出的原则程序应该先销毁spgirl(但无论是不是这样都无所谓因为造成这个问题的根本原因时这两个智能指针不能被同时销毁,得有个先后)

首先销毁spgirl的时候,还没轮到spboy被销毁,因此它指向的Boy类型的堆区内存还存在,这就意味着Boy的成员属性_girlfriend也存在,因此_girlfriend指向的Girl类型的堆区内存也存在,这就意味着Girl的成员属性_boyfriend也存在,因此Boy类型的堆区内存还存在。即便spboy销毁掉问题也会陷入这种类似死锁的问题。Boy类型的堆区内存想要释放,就必须释放掉_boyfriend,_boyfriend想要释放掉,就必须释放掉Girl类型的堆区内存,Girl类型的堆区内存想要释放掉就必须释放掉_girlfriend,_girlfriend想要释放掉就必须释放掉Boy类型的堆区内存.....反复循环好像被牢牢锁住。

下面的例子也会发生这种情况:

class Girl {
public:
    Girl() {
        cout << "Girl的构造" << endl;
    }
    ~Girl() {
        cout << "~Girl 析构函数" << endl;
    }
    shared_ptr<Girl>_girlfriend;
};
int main() {
    shared_ptr<Girl>spgirl(new Girl);//Girl的堆区内存被spgirl和Boy的共享指针类型的成员属性同时指向
    shared_ptr<Girl>spgirlgirl(new Girl);//Girl的堆区内存被spgirl和Boy的共享指针类型的成员属性同时指向
    spgirl->_girlfriend = spgirlgirl;
    spgirlgirl->_girlfriend = spgirl;
    
    system("pause");
    return 0;
}

weak_ptr

它是一个辅助,赋值某一shared_ptr;它是一个检测者,检测某一块被shared_ptr管理内存,而它本身是不能管理这块内存的(比如释放这块内存,使用*或则->),因此需要先创建一个shared_ptr然后使用左值赋值或则左值赋值构造给weak_ptr赋值

use_cout()

用来记录有多少个共享指针指向它检测的内存

expired()

用来检测它检测的内存是否被释放,是的话返回true,否则返回false。

lock()

用来返回一个可用的shared_ptr对象

注意以下程序(但不知道具体原因)

weak_ptr<int>wk;
auto sp1 = make_shared<int>(10);
shared_ptr<int>sp2;
sp2 = sp1;
wk = sp1;

cout << sp2.use_count() << endl;
cout << wk.lock().use_count() << endl;
cout << wk.lock().use_count() << endl;
cout << sp2.use_count() << endl;

reset()

weak_ptr指针不再检测原来的内存

1 weak_ptr<int>wk;
2 auto sp1 = make_shared<int>(10);
3 shared_ptr<int>sp2;
4 sp2 = sp1;
5 wk = sp1;
6 wk.reset();
7 cout << wk.expired() << endl;//不再检测原来的内存,此时再判断它检测的内存是否被释放结果应该返回true

share_ptr陷阱

不能让同一个堆区内存初始化多个共享指针,因为这块堆区内存会被释放多次。

1 int* p = new int(10);
2 shared_ptr<int>sp1(p);
3 shared_ptr<int>sp2(p);

类中成员函数不能返回一个管理this指针的共享指针

 1 class Test {
 2 public:
 4     shared_ptr<Test> fun() {
 5         return shared_ptr<Test>(this);//返回的是一个共享指针要用共享指针接收它
 6     }
 7     ~Test() {
 8         cout << "Test disappear" << endl;
 9     }
10 };
11 int main() {
25     shared_ptr<Test>sp1(new Test);
26     cout << "sp1.use_count() first:" << sp1.use_count() << endl;
27     //以上实例化的Test类内存通过sp1调用它的成员函数fun(),此时this指向这块类内存
28     //因此fun()函数返回的是这块类内存,那么这就导致了一个堆区内存初始化了两个共享指针
29     shared_ptr<Test>sp2 = sp1->fun();
30     cout << "sp1.use_count() second:" << sp1.use_count() << endl;
31     cout << "sp2.use_count():" << sp2.use_count() << endl;
32     system("pause");
33     return 0;
34 }

解决循环引用问题

一块内存只要没有共享指针指向它,它就会被释放不管有没有weak_ptr监视它

 

1 weak_ptr<Girl>wpgirl = spgirl;
2 cout << wpgirl.expired() << endl;
3 spgirl.reset();
4 cout << wpgirl.expired() << endl;

 

 

因此我们只需要将一个类中的共享智能指针对象改成weak_ptr就可以了

 

 1 class Girl;
 2 class Boy {
 3 public:
 4     Boy() {
 5         cout << "Boy的构造" << endl;
 6     }
 7     ~Boy() {
 8         cout << "~Boy 析构函数" << endl;
 9     }
10     weak_ptr<Girl>_girlfriend;
11 };
12 class Girl {
13 public:
14     Girl() {
15         cout << "Girl的构造" << endl;
16     }
17     ~Girl() {
18         cout << "~Girl 析构函数" << endl;
19     }
20     shared_ptr<Boy>_boyfriend;
21 };
22 
23 int main() {
24 
25     shared_ptr<Girl>spgirl(new Girl);//Girl的堆区内存被spgirl和Boy的共享指针类型的成员属性同时指向
26     shared_ptr<Boy>spboy(new Boy);//Girl的堆区内存被spgirl和Boy的共享指针类型的成员属性同时指向
27     spgirl->_boyfriend = spboy;
28     spboy->_girlfriend = spgirl;
29 
30     system("pause");
31     return 0;
32 }

 

 

首先销毁spgirl的时候,还没轮到spboy被销毁,因此它指向的Boy类型的堆区内存还存在,这就意味着Boy的成员属性_girlfriend也存在,但是_girlfriend是弱指针,没有任何共享指针指向Girl类型的堆区内存,因此Girl内存被释放_boyfriend也跟着被释放,因此spboy被销毁的时候没有任何共享指针指向Boy这块堆区内存,因此Boy这块堆区内存被释放。

 

posted on 2023-09-03 21:50  小凉拖  阅读(16)  评论(0编辑  收藏  举报