智能指针与STL查漏补缺(1)

目录

一、智能指针的本质原理

1、为什么需要智能指针

(1)智能指针是为了解决内存泄漏问题

(2)内存泄漏问题,本质上是因为程序员自己忘记主动释放导致的

(3)智能指针的解决思路是:申请内存使用完后并自动释放

2、智能指针的本质工作原理

(1)一方面,利用局部变量/对象分配在栈上,代码段结束时会自动释放的特性

(2)另一方面,利用对象释放时会自动调用析构函数的特性

3、智能指针演示代码实战

(1)定义一个类TestPtr,写出构造函数和析构函数
(2)main.cpp中写一个全局函数func,在main中调用执行func
(3)func中定义一个TestPtr类的对象作为局部变量,测试验证程序执行后自动调用析构函数
(4)在TestPtr中定义成员变量int *p, 构造中new申请空间给p,析构中delete释放
(5)在TestPtr中写test_use函数,使用p所指向的内存
(6)分析以上,TestPtr使用了动态内存但是并不会导致内存泄漏,这就是智能指针的套路
#include <iostream>
using namespace std;

class TestPtr
{
private:
    int *p;//用于指向申请的动态内存

public:
    TestPtr()
    {
        cout << "TestPtr()" << endl;
        p = NULL;
    }
    TestPtr(int size)
    {
        cout << "TestPtr(int size)" << endl;
        p = new int[size];
    }
    ~TestPtr()
    {
        cout << "~TestPtr()" << endl;
        if(NULL != p)
            delete[] p;
    }

    void testprint(void);
};

void TestPtr::testprint(void)
{
    if (NULL != p)
    {
        p[0] = 12;
        cout << "p[0] = " << p[0] << endl;
    }
    else
    {
        cout << "p = NULL" << endl;
    }

}

void UseTest(void)
{
    TestPtr tp(5);
    tp.testprint();
}

int main(int argc, char *argv[])
{
    UseTest();
    return 0;
}

4、总结

(1)智能指针本身是一个类,而普通指针本身是一个变量

(2)智能指针的“智能”指的是随着智能指针对象本身的释放,所指向的对象也释放

(3)C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用

参考学习:https://zh.cppreference.com/w/cpp/memory
智能指针与STL查漏补缺(1)

二、auto_ptr的使用

参考学习:https://zh.cppreference.com/w/cpp/memory/auto_ptr

1、auto_ptr的常规使用

智能指针与STL查漏补缺(1)

2、auto_ptr的成员方法

智能指针与STL查漏补缺(1)

#include <iostream>
#include <memory>

using namespace std;

class people
{
private:
    string name;
public:
    people(){};
    people(string s)
    {
        name = s;
        cout << "people(string s)" << endl;
    }
    ~people()
    {
        //虚构函数内部一般可能会有动态内存的释放,虽然我们这里未提供
        cout << "~people()" << endl;

    }

    void print(void)
    {
        cout << "name = " << this->name << endl;
    }
};


int main(int argc, char *argv[])
{

    //示例1,不适用智能指针时
    people p("linux1");//定义people类的一个对象p
    p.print();
    people *p1 = new people("linux1");//定义一个指向 people类的一个对象 的指针
    p1->print();
    delete p1;//需要手动进行释放,否则会发生内存泄漏,通过调试可知,若无该语句,则不会执行析构函数

    //示例二:使用智能指针实现上述示例
    auto_ptr<people> p2(new people("linux2"));
    p2->print();
    auto_ptr<people> p3 = p2;
    p3->print();
    //p2->print();//这句不隐掉,则会出现段错误,因为经过赋值后,p2已经失去了对他指向的那个
                  //操作的权力,若p2、p3都能操作的话,则会出现段错误,因为二者结束时都会调用
                  //析构函数,而内存释放只可进行一次,再次释放就会报错,因为已经释放掉了    

    p3.reset(new people("harmonyos"));//将被指向的对象从linux2换位harmonyos,替换时会先
    p3->print();                      //释放掉linux2

    people *p4 = p3.release();//p3释放掉自己所指向的那个对象,无法再进行操作
    //p3->print();//释放后无法操作,会报段错误
    p4->print();
    delete p4;//经过上面操作,现在使用的不是指针了,需要我们手动进行内存的释放

    return 0;
}

3、auto_ptr的弊端

参考学习(必看):https://blog.csdn.net/czc1997/article/details/84026887

三、unique_ptr的使用及构造函数

智能指针与STL查漏补缺(1)
智能指针与STL查漏补缺(1)
使用unique_ptr如果不传deleter,则使用的就是默认的(析构函数),这就和auto_ptr是一样的,但如果提供了deleter则与前面的情况不同,对应于上边图片中两个不同的unique_ptr重载

参考学习:
https://zh.cppreference.com/w/cpp/memory/unique_ptr
https://zh.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr(重难点)
 
注:本专栏博客提供关于C++手册的网址链接,由于服务器在国外,所以访问速度比较慢,
有时电脑无法打开,可尝试在手机上使用手机流量打开网址(亲试有效),当然你要是会翻
墙就另说了

智能指针与STL查漏补缺(1)

#include <iostream>
#include <memory>
 
struct Foo { // 要管理的对象
    Foo() { std::cout << "Foo ctor\n"; }//构造函数
    Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }//拷贝构造函数
    Foo(Foo&&) { std::cout << "Foo move ctor\n"; }//移动构造函数
    ~Foo() { std::cout << "~Foo dtor\n"; }//析构函数
};
 
struct D { // 删除器
    D() {};//默认构造函数
    D(const D&) { std::cout << "D copy ctor\n"; }//拷贝构造函数
    D(D&) { std::cout << "D non-const copy ctor\n";}//非const型的拷贝构造函数
    D(D&&) { std::cout << "D move ctor \n"; }//移动构造函数
    void operator()(Foo* p) const {//函数对象
    	//在这里除了调用delete还可以增加一些其他的设计和封装,这也是
    	//unique_ptr和auto_ptr不同的地方
        std::cout << "D is deleting a Foo\n";
        delete p;
    };
};
 
int main()
{
    std::cout << "Example constructor(1)...\n";
    std::unique_ptr<Foo> up1;  // up1 为空,定义一个空指针,并未绑定对象,故不会调用构造函数
    std::unique_ptr<Foo> up1b(nullptr);  // up1b 为空,同上
 
    std::cout << "Example constructor(2)...\n";
    {
        std::unique_ptr<Foo> up2(new Foo); // up2 现在占有 Foo,绑定了对象调用构造函数
    } // Foo 的作用域结束,其被删除,在{}这个代码块内
 
    std::cout << "Example constructor(3)...\n";
    D d;
    {  // 删除器类型不是引用
       std::unique_ptr<Foo, D> up3(new Foo, d); // 复制删除器
    }
    {  // 删除器类型是引用 
       std::unique_ptr<Foo, D&> up3b(new Foo, d); // up3b 保有到 d 的引用,故不需要调用构造函数
    }
 
    std::cout << "Example constructor(4)...\n";
    {  // 删除器不是引用
       std::unique_ptr<Foo, D> up4(new Foo, D()); // 移动删除器,D():调用构造函数,对应移动语义
    }
 
    std::cout << "Example constructor(5)...\n";
    {
       std::unique_ptr<Foo> up5a(new Foo);
       std::unique_ptr<Foo> up5b(std::move(up5a)); // 所有权转移
    }
 
    std::cout << "Example constructor(6)...\n";
    {
        std::unique_ptr<Foo, D> up6a(new Foo, d); // 复制 D
        std::unique_ptr<Foo, D> up6b(std::move(up6a)); // 移动 D
 
        std::unique_ptr<Foo, D&> up6c(new Foo, d); // D 是引用
        std::unique_ptr<Foo, D> up6d(std::move(up6c)); // 复制 D
    }
 
#if (__cplusplus < 201703L)
    std::cout << "Example constructor(7)...\n";
    {
        std::auto_ptr<Foo> up7a(new Foo);
        std::unique_ptr<Foo> up7b(std::move(up7a)); // 所有权转移
    }
#endif
 
    std::cout << "Example array constructor...\n";
    {
        std::unique_ptr<Foo[]> up(new Foo[3]);
    } // 删除三个 Foo 对象
}

输出:
Example constructor(1)...
Example constructor(2)...
Foo ctor
~Foo dtor
Example constructor(3)...
Foo ctor
D copy ctor
D is deleting a Foo
~Foo dtor
Foo ctor
D is deleting a Foo
~Foo dtor
Example constructor(4)...
Foo ctor
D move ctor 
D is deleting a Foo
~Foo dtor
Example constructor(5)...
Foo ctor
~Foo dtor
Example constructor(6)...
Foo ctor
D copy ctor
D move ctor 
Foo ctor
D non-const copy ctor
D is deleting a Foo
~Foo dtor
D is deleting a Foo
~Foo dtor
Example constructor(7)...
Foo ctor
~Foo dtor
Example array constructor...
Foo ctor
Foo ctor
Foo ctor
~Foo dtor
~Foo dtor
~Foo dtor

四、unique_ptr的其他成员方法详解

智能指针与STL查漏补缺(1)
智能指针与STL查漏补缺(1)
参考学习:https://zh.cppreference.com/w/cpp/memory/unique_ptr
这些方法的使用与auto_ptr相似。
智能指针与STL查漏补缺(1)

#include <iostream>
#include <memory>
using namespace std;

struct Foo
{
    void bar(){std::cout << "Foo::bar\n";}
};

void f(const Foo& foo)
{
    cout << "f(const Foo& foo)" << endl;
}

int main(int argc, char *argv[])
{
    //示例一:单对象版本的元素访问
    unique_ptr<Foo> p(new Foo);
    p->bar();
    (*p).bar();
    f(*p);

    //示例二:数组版本的元素访问
    const int size = 10;
    unique_ptr<int[]> fact(new int[size]);

    for(int i = 0; i < 5; i++)
    {
        fact[i] = (i==0) ? : i * fact[i-1];
    }

    for(int i = 0; i < 5; i++)
    {
        cout << i << " : " << fact[i] << "\n";
    }

    return 0;
}

五、unique_ptr的对比和总结

1、unique_ptr比auto_ptr的优势

(1)消费unique_ptr的函数能以值或以右值引用接收它

unique_ptr<People> pass_through(unique_ptr<People> p)
{
	p->bar();
	return p;
}

(2)unique_ptr在智能指针变量赋值方面比auto_ptr安全

unique_ptr<string> pu1(new string ("hello world")); 
unique_ptr<string> pu2; 
pu2 = pu1;                                      // 不允许这样操作

unique_ptr<string> pu3; 
pu3 = unique_ptr<string>(new string ("You"));   // 允许

2、unique_ptr的不足和使用注意事项

(1)不要与裸指针混用

int *x(new int());
	unique_ptr<int> up1,up2;
	up1.reset(x);	
	up2.reset(x);	//会使up1 up2指向同一个普通对象,将来程序结束时会导致内存二次释放,出现报错

(2)不能忘记接收u.release()方法的返回值

3、unique_ptr总结

(1)unique_ptr 之所以叫这个名字,是因为它只能指向一个对象,当它指向新的对象时,之前所指向的对象会被释放。

(2)当unique_ptr本身被释放时,指向的对象也会被自动释放

(3)unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。

(4)要安全的重用unique_ptr,可以用reset给他赋新值。

(5)std::move()能够将一个unique_ptr赋给另一个。这样转移所有权后 还是有可能出现原有指针调用就崩溃的情况。但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。

(6)C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类

注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

上一篇:96. Unique Binary Search Trees


下一篇:Android活动变暗,失去焦点并冻结-没有错误消息,并且在首次运行后有效