c++智能指针

文章目录

  • 前言
  • 一、智能指针的种类
  • 二、智能指针的概述
  • 三、独占指针:unique_ptr
    • 1、unique_ptr三种创建方式
      • 1、通过已有裸指针创建
      • 2、通过new创建
      • 3、通过std::make_unique创建(推荐)
  • 四、unique_ptr与函数调用
    • 1、函数调用与unique_ptr注意事项
      • 1、passing by value
      • 2、passing by ref
      • 3、return by value
  • 五、计数指针:shared_ptr
  • 六、shared_ptr与函数
  • 七、shared_ptr与unique_ptr的转化
  • 八、weak_ptr:shared_ptr的补充


前言

在C++11中通过引入智能指针的概念,使得C++程序员不需要手动释放内存。

一、智能指针的种类

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

注意:std::auto_ptr已被废弃

二、智能指针的概述

C++的指针包括两种

  • 原始指针(raw pointer)
  • 智能指针

智能指针是对原始指针的封装,其优点是自动分配内存,不用担心潜在的内存泄漏。并不是所有的指针都可以封装成智能指针,很多时候原始指针要更方便。
各种指针中,最常用的是裸指针(原始指针),其次是unique_ptr和shared_ptr,weak_ptr是shared_ptr的一个补充,应用场景较少。
智能指针与Rust的内存安全
智能指针只解决一部分问题,即独占/共享所有权指针的释放、传输。但智能指针没有从根本上解决C++内存安全问题,不加以注意依然会造成内存安全问题。

三、独占指针:unique_ptr

unique_ptr在任何给定的时刻,只能有一个指针管理内存,当指针超出作用域时,内存将自动释放,该类型指针不可Copy,只可以Move。

三种创建方式:

  • 通过已有裸指针创建
  • 通过new来创建
  • 通过std::make_unique创建(推荐)

unique_ptr可以通过get()获取地址
unique_ptr实现了->与*

  • 可以通过->调用成员函数
  • 可以通过*调用dereferencing

cat.h

#ifndef POINTER_CAT_H
#define POINTER_CAT_H


#include 
#include 

class Cat
{
public:
    Cat(std::string name);
    Cat() = default;
    ~Cat();
    // ->
    void cat_info() const
    {
        std::cout << "cat info name: " << name << std::endl;
    }
    std::string get_name()
    {
        return name;
    }
    void set_name(const std::string &name)
    {
        this->name = name;
    }
private:
    std::string name;
};

#endif //POINTER_CAT_H

cat.cpp

#include "cat.h"

Cat::Cat(std::string name):name(name)
{
    std::cout << "Construct of Cat: " << name << std::endl;
}

Cat::~Cat()
{
    std::cout << "Destructor of Cat: " << name << std::endl;
}

不加指针 main.cpp

#include "cat.h"
#include 

using namespace std;

int main(int argc, char *argv[])
{
    // stack
    Cat c("OK");
    c.cat_info();

    {
        Cat c("OK");
        c.cat_info();
    }

    cout << "ending......" << endl;
    return 0;
}

输出

Construct of Cat: OK
cat info name: OK
Construct of Cat: OK
cat info name: OK
Destructor of Cat: OK  //局部作用域结束就会自动析构
ending......
Destructor of Cat: OK

原始指针main.cpp

#include "cat.h"
#include 

using namespace std;

int main(int argc, char *argv[])
{
    Cat *c_p1 = new Cat("kiki");
    c_p1->cat_info();

    {
        Cat *c_p1 = new Cat("gigi");
        c_p1->cat_info();
    }

    cout << "ending......" << endl;
    return 0;
}
Construct of Cat: kiki
cat info name: kiki
Construct of Cat: gigi
cat info name: gigi
ending......

堆上的空间不会自动释放,需要手动调用delete

#include "cat.h"
#include 

using namespace std;

int main(int argc, char *argv[])
{
    Cat *c_p1 = new Cat("kiki");
    c_p1->cat_info();

    {
        Cat *c_p1 = new Cat("gigi");
        c_p1->cat_info();
        delete c_p1;
    }
	delete c_p1;
    cout << "ending......" << endl;
    return 0;
}
Construct of Cat: kiki
cat info name: kiki
Construct of Cat: gigi
cat info name: gigi
Destructor of Cat: gigi
Destructor of Cat: kiki
ending......

上面是自定义类型,下面换成系统提供的类型

int main(int argc, char *argv[])
{
    int *i = new int(100);
    {
        int *i = new int(200);
    }
    cout << *i << endl;
    cout << "ending......" << endl;
    return 0;
}
100
ending......

修改一下

int main(int argc, char *argv[])
{
    int *i = new int(100);
    {
        i = new int(200);
    }
    cout << *i << endl;
    cout << "ending......" << endl;
    return 0;
}
200
ending......

再修改一下

int main(int argc, char *argv[])
{
    int *i = new int(100);
    {
        i = new int(200);
        delete i;
    }
    cout << *i << endl;
    delete i;
    cout << "ending......" << endl;
    return 0;
}
pointer(1070,0x104bdc580) malloc: *** error for object 0x600003f20040: pointer being freed was not allocated

程序报错,上述代码存在的问题是第二次delete,并且第一次开辟的空间100没有释放,造成内存泄漏。也就是说,在使用原始指针时,每次new都需要delete,忘记delete或者额外的delete都可能造成程序错误,所以是不安全不方便的。

1、unique_ptr三种创建方式

1、通过已有裸指针创建

	Cat *c_p = new Cat("kiki");
    std::unique_ptr<Cat> u_c_p{c_p};
    c_p->cat_info(); // cat info name: kiki
    u_c_p->cat_info(); // cat info name: kiki
    // 当使用原始指针改变对象时,独占指针也会随之改变
    // 这样就不满足"独占"的含义
    c_p->set_name("haha");
    u_c_p->cat_info(); // cat info name: haha
    // 建议销毁原始指针
    c_p = nullptr;
    delete c_p;
    u_c_p->cat_info(); // cat info name: haha
    cout << "ending......" << endl;

2、通过new创建

	std::unique_ptr<Cat> u_ptr{new Cat("dd")};
    u_ptr->cat_info();
    u_ptr->set_name("haha");
    u_ptr->cat_info();
    cout << "ending......" << endl;

3、通过std::make_unique创建(推荐)

这种方式只有c++14才支持

	std::unique_ptr<Cat> u_ptr = make_unqiue<Cat>();
//  std::unique_ptr u_i = make_unique(100);
//  cout << u_i.get() << endl;
    u_ptr->cat_info();
    u_ptr->set_name("haha");
    u_ptr->cat_info();

以上三种方式都会自动调用析构函数

四、unique_ptr与函数调用

unique_ptr是不可copy,只可以move,在做函数参数或者返回值中一定要注意所有权。

1、函数调用与unique_ptr注意事项

  • Passing by value
    需要使用std::move来转移内存拥有权
    如果参数直接传入std::make_unique语句自动转换为move
  • Passing by reference
    如果设置参数为const则不能改变指向(比如reset()方法,reset()方法为智能指针清空方法)
  • Return by value
    指向一个local object,可以用作链式函数

1、passing by value

void do_with_cat_pass_value(std::unique_ptr<Cat> c)
{
    c->cat_info();
}
	std::unique_ptr<Cat> c{new Cat("kiki")};
    do_with_cat_pass_value(std::move(c));
    // do_with_cat_pass_value(std::make_unique()); // 隐式转换
    // c->cat_info(); /// 已经move 调用报错
    cout << "ending......" << endl;
    return 0;

2、passing by ref

不加const

void do_with_cat_pass_ref(std::unique_ptr<Cat> &c)
{
    c->set_name("haha");
    c->cat_info();
    c.reset();
}
	std::unique_ptr<Cat> u_p{new Cat("kiki")};
    // 不加const
    do_with_cat_pass_ref(u_p);
    cout << "address:" << u_p.get() << endl; // 0x0
    cout << "ending......" << endl;
    return 0;

加const

void do_with_cat_pass_ref(const std::unique_ptr<Cat> &c)
{
    c->set_name("haha"); // 居然还可以修改
    c->cat_info();
    // c.reset();
}
	std::unique_ptr<Cat> u_p{new Cat("kiki")};
    // 不加const
    do_with_cat_pass_ref(u_p);
    cout << "address:" << u_p.get() << endl; 
    cout << "ending......" << endl;
    return 0;

3、return by value

std::unique_ptr<Cat> get_unique_ptr()
{
    std::unique_ptr<Cat> u_p{new Cat("local cat")};
    cout << u_p.get() << endl;
    cout << &u_p << endl;
    // get返回的是指针指向的内存地址
    // &返回的是指针的地址
    return u_p;
}
	get_unique_ptr()->cat_info();
    cout << "ending......" << endl;
    return 0;

五、计数指针:shared_ptr

shared_ptr计数指针又称共享指针,与unique_ptr不同的是它是可以共享数据的。shared_ptr创建了一个计数器与泪对象所指的内存相关联,copy则计数器加一,销毁则计数器减一,查看计数的api为use_count().

常量类型

	std::shared_ptr<int> s_p1 = make_shared<int>(10);
    cout << "value:" << *s_p1 << endl;  // 10
    cout << "use count:" << s_p1.use_count() << endl; // 1
    std::shared_ptr<int> s_p2 = s_p1;
    cout << "s_p1 use count:" << s_p1.use_count() << endl; // 2
    cout << "s_p2 use count:" << s_p2.use_count() << endl; // 2

    *s_p2 = 30;
    cout << "value:" << *s_p1 << endl;  // 30
    cout << "s_p1 use count:" << s_p1.use_count() << endl; // 2
    cout << "s_p2 use count:" << s_p2.use_count() << endl; // 2

    s_p2 = nullptr;
    cout << "s_p1 use count:" << s_p1.use_count() << endl; // 1
    cout << "s_p2 use count:" << s_p2.use_count() << endl; // 0
    return 0;

自定义数据类型

	std::shared_ptr<Cat> c_p1 = make_shared<Cat>();
    cout << "c_p1 use_count:" << c_p1.use_count() << endl;
    std::shared_ptr<Cat> c_p2 = c_p1;
    std::shared_ptr<Cat> c_p3 = c_p2;
    cout << "c_p1 use_count:" << c_p1.use_count() << endl;
    cout << "c_p2 use_count:" << c_p2.use_count() << endl;
    cout << "c_p3 use_count:" << c_p3.use_count() << endl;
    c_p1.reset();
    cout << "c_p1 use_count:" << c_p1.use_count() << endl;
    cout << "c_p2 use_count:" << c_p2.use_count() << endl;
    cout << "c_p3 use_count:" << c_p3.use_count() << endl;
    return 0;

注意:不管计数是多少,数据在内存中只有一份,一次构造,一次销毁。

六、shared_ptr与函数

  • shared_ptr passed by value
    copy
    函数内部计数器加一

    void cat_by_value(std::shared_ptr<Cat> cat)
    {
        cout << cat->get_name() << endl;
        cat->set_name("haha");
        cout << "func use count:" << cat.use_count() << endl; // 2
    }
    
    std::shared_ptr<Cat> c1 = make_shared<Cat>("kiki");
        cat_by_value(c1);
        c1->cat_info(); // haha
        cout << "use count" << c1.use_count() << endl; //1
    
  • shared_ptr passed by ref
    const表示不可改变指向

    void cat_by_value(std::shared_ptr<Cat> cat)
    {
        cout << cat->get_name() << endl;
        cat->set_name("haha");
        cout << "func use count:" << cat.use_count() << endl; // 2
    }
    void cat_by_ref(std::shared_ptr<Cat> &cat)
    {
        cout << cat->get_name() << endl;
        cat.reset(new Cat("dd")); // 加了const就不能调用
        cout << "func use count:" << cat.use_count() << endl;
    }
    
    	std::shared_ptr<Cat> c1 = make_shared<Cat>("kiki");
        cat_by_value(c1);
        c1->cat_info(); // haha
        cout << "use count" << c1.use_count() << endl; //1
    
        cat_by_ref(c1);
        c1->cat_info();
    
        return 0;
    
    Construct of Cat: kiki
    kiki
    func use count:2
    cat info name: haha
    use count1
    haha
    Construct of Cat: dd
    Destructor of Cat: haha
    func use count:1
    cat info name: dd
    Destructor of Cat: dd
    
  • returning by value
    链式调用

    std::shared_ptr<Cat> get_shared_ptr()
    {
        std::shared_ptr<Cat> cat_ptr = make_shared<Cat>("local cat");
        return cat_ptr;
    }
    

七、shared_ptr与unique_ptr的转化

不能将shared_ptr转换为unique_ptr,unique_ptr可以转换为shared_ptr(通过std::move)
常见的设计:将你的函数返回unique_ptr是一种常见的设计模式,这样可以提高代码的复用度,你可以随时改变为shared_ptr

	std::unique_ptr<Cat> c_p1 = unique_ptr<Cat>{new Cat("kiki")};
    std::shared_ptr<Cat> c_p2 = std::move(c_p1);
    cout << c_p2.use_count() << endl; //1

    std::shared_ptr<Cat> c_p3 = get_unique_ptr(); // 隐式转换
    if(c_p3){
        cout << c_p3.use_count() << endl; //1
    }

八、weak_ptr:shared_ptr的补充

weak_ptr并不拥有内存的所有权,并不能调用->和解引用*。
为什么需要weak_ptr呢?
假设A类中有一个需求需要存储其他A类对象的信息,如果使用shared_ptr,那么在销毁时会遇到循环依赖问题(Cyclic dependency problem),所以这里需要一个不需要拥有所有权的指针来标记该同类对象(weak_ptr可以通过lock()函数来提升为shared_ptr(类型转换))
cat.h

#ifndef POINTER_CAT_H
#define POINTER_CAT_H


#include 
#include 
#include 
class Cat
{
public:
    Cat(std::string name);
    Cat() = default;
    ~Cat();
    // ->
    void cat_info() const
    {
        std::cout << "cat info name: " << name << std::endl;
    }
    std::string get_name()
    {
        return name;
    }
    void set_name(const std::string &name)
    {
        this->name = name;
    }
    void set_friend(std::shared_ptr<Cat> c)
    {
        m_friend = c;
    }
private:
    std::string name;
    std::shared_ptr<Cat> m_friend;
};

#endif //POINTER_CAT_H
 std::shared_ptr<Cat> s_p1 = std::make_shared<Cat>("kiki");
    std::weak_ptr<Cat> w_p1(s_p1);

    cout << "s_p1 use count:" << s_p1.use_count() << endl; // 1
    // weak_ptr可以调用use_count,但计数并不会加1
    cout << "w_p1 use count:" << w_p1.use_count() << endl; // 1
    // w_p1.cat_info() 报错
    std::shared_ptr<Cat> s_p2 = w_p1.lock();
    cout << "s_p1 use count:" << s_p1.use_count() << endl; // 2
    cout << "s_p1 use count:" << s_p2.use_count() << endl; // 2
    cout << "w_p1 use count:" << w_p1.use_count() << endl; // 2
    
    cout << "##############循环依赖##############" << endl;
    // 循环依赖
    std::shared_ptr<Cat> cat1 = std::make_shared<Cat>("cat1");
    std::shared_ptr<Cat> cat2 = std::make_shared<Cat>("cat2");

    return 0;
Construct of Cat: kiki
s_p1 use count:1
w_p1 use count:1
s_p1 use count:2
s_p1 use count:2
w_p1 use count:2
##############循环依赖##############
Construct of Cat: cat1
Construct of Cat: cat2
Destructor of Cat: cat2
Destructor of Cat: cat1
Destructor of Cat: kiki

目前上述代码没有任何问题

 	 std::shared_ptr<Cat> cat1 = std::make_shared<Cat>("cat1");
    std::shared_ptr<Cat> cat2 = std::make_shared<Cat>("cat2");
    // 将cat1的朋友设置为cat2
    // 将cat2的朋友设置为cat1
    cat1->set_friend(cat2);
    cat2->set_friend(cat1);
Construct of Cat: kiki
s_p1 use count:1
w_p1 use count:1
s_p1 use count:2
s_p1 use count:2
w_p1 use count:2
##############循环依赖##############
Construct of Cat: cat1
Construct of Cat: cat2
Destructor of Cat: kiki

此时,cat1和cat2由于循环依赖并没有正常销毁,而程序也正常退出了。
解决这个问题,只需要将cat.h中的std::shared_ptr改为weak_ptr即可。

Construct of Cat: kiki
s_p1 use count:1
w_p1 use count:1
s_p1 use count:2
s_p1 use count:2
w_p1 use count:2
##############循环依赖##############
Construct of Cat: cat1
Construct of Cat: cat2
Destructor of Cat: cat2
Destructor of Cat: cat1
Destructor of Cat: kiki

修改后,都正常销毁了!

你可能感兴趣的:(cpp基本语法,c++,开发语言)