c++智能指针


前言

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

一、智能指针的种类

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

注意std::auto_ptr已被废弃

二、智能指针的概述

C++的指针包括两种

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

智能指针是对原始指针的封装其优点是自动分配内存不用担心潜在的内存泄漏。并不是所有的指针都可以封装成智能指针很多时候原始指针要更方便。
各种指针中最常用的是裸指针原始指针其次是unique_ptr和shared_ptrweak_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 <string>
#include <iostream>

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 <memory>

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 <memory>

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 <memory>

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<int> u_i = make_unique<int>(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<Cat>()); // 隐式转换
    // 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_ptrunique_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 <string>
#include <iostream>
#include <memory>
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

修改后都正常销毁了

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++