1、智能指针概念
-
C++11中通过引入智能指针的概念,使得C++程序员不需要手动释放内存
-
智能指针的种类
- std::unique_ptr:独占指针,只能有一个智能指针管理内存。
- std::shared_ptr:共享指针、计数指针;可以多个指针指向同一个内存地址
- std::weak_ptr:弱指针
- std::auto_ptr:已经被废掉了
-
C++的指针主要包括两种:
- 原始指针:也叫裸指针,是最常用的指针,原始指针更加的方便
- 智能指针:智能指针是原始指针的封装,其优点是会自动分配内存,不用担心潜在的内存泄漏问题;但只是解决了一部分问题,即独占、共享所有权指针的释放、传输
2、独占指针unique_ptr
- unique_ptr在任何给定的时刻,只能有一个智能指针管理内存
- 当指针超出作用域时,内存将自动释放
- 该智能指针只能Move,不能Copy
- unique_ptr的三种创建方式:
- 通过已有的原始指针进行创建,此时原始指针和智能指针都指向同一个地址(并不独占),而unique_ptr独占的意思是指只能有一个智能指针管理这一块内存
- 通过new来创建
- 通过std::make_unique来创建(推荐方式)
- unique_ptr可以通过get()函数来获取指针的地址,可以通过->来调用成员函数、通过*来解引用。
先准备一个Cat.h和一个Cat.cpp,如下
#ifndef INC_02_SMART_POINTER_CAT_H
#define INC_02_SMART_POINTER_CAT_H
#include <iostream>
#include <string>
class Cat{
private:
std::string name{"Mimi"};
public:
Cat() = default;
Cat(std::string _name);
std::string get_Name() const{
return name;
}
void set_Name(const std::string &_name){
this->name = _name;
}
void catInfo() const {
std::cout << "cat info name: " << name << std::endl;
}
virtual ~Cat();
};
#endif //INC_02_SMART_POINTER_CAT_H
Cat::Cat(std::string _name): name(_name){
std::cout << "Constructor of Cat: " << name << std::endl;
}
Cat:: ~Cat() {
std::cout << "Destructor of Cat: " << name << std::endl;
}
2.1、基础知识
- 下面这个代码在{}作用域结束后都不会主动释放分配在heap堆上的c1的内存空间,并且两个c1的内存地址并不一样。
- 因此在不需要这个指针时需要及时的释放空间,否则只能等整个程序运行结束操作系统来回收。
void test_heap()
{
Cat *c1 = new Cat("Hello");
int *i = new int(100);
std::cout << "test_heap c1 address: " << c1 <<std::endl;
c1->catInfo();
{
Cat *c1 = new Cat("World");
*i = 123;
std::cout << "{ } c1 address: " << c1 <<std::endl;
c1->catInfo();
// delete c1;
}
std::cout << "*i = " << *i << std::endl;
// delete c1;
// delete i;
std::cout << __func__ << std::endl;
}
2.2、智能指针创建(不推荐)
- 通过一个创建好的对象cat,让unique_ptr指向cat,此时ptr和cat都指向同一片空间,一旦cat空间释放,ptr也用不了。
- 这其实是一种bad smell,因为两个指针指向同一个地址,都已经有原始指针了用智能指针再次包装显得毫无意义。
void test_unique_ptr1()
{
Cat *cat = new Cat("小猫");
std::unique_ptr<Cat> ptr{cat};
cat->catInfo();
ptr->catInfo();
cat->set_Name("Mimi");
cat->catInfo();
ptr->catInfo();
}
/* 输出
Constructor of Cat: 小猫
cat info name: 小猫
cat info name: 小猫
cat info name: Mimi
cat info name: Mimi
test_unique_ptr1 // 函数的作用域结束之前的cout
Destructor of Cat: Mimi // 由智能指针进行地址的释放
*/
2.3、智能指针创建(可用)
void test_unique_ptr2()
{
std::unique_ptr<Cat> ptr{new Cat("Hello")};
ptr->catInfo();
ptr->set_Name("Mini");
ptr->catInfo();
std::cout << __func__ << std::endl;
}
/* 输出
Constructor of Cat: Hello
cat info name: Hello
cat info name: Mini
test_unique_ptr2
Destructor of Cat: Mini //同上
*/
2.4、智能指针创建(推荐)
void test_unique_ptr3()
{
std::unique_ptr<Cat> ptr = std::make_unique<Cat>("小猫");
ptr->catInfo();
{
std::unique_ptr<Cat> ptr = std::make_unique<Cat>(); // 调用默认构造函数,name = "Mini"
ptr->catInfo();
}
std::cout << __func__ << std::endl;
}
/* 输出
Constructor of Cat: 小猫
cat info name: 小猫
cat info name: Mimi
Destructor of Cat: Mimi // {}作用域结束自动释放
test_unique_ptr3
Destructor of Cat: 小猫 // 函数声明周期结束自动释放
*/
2.5、智能指针获取地址
- 可以通过get函数获取智能指针中的指针地址,&ptr表示获取智能指针的地址
void test_unique_ptr4()
{
std::unique_ptr<Cat> cat = std::make_unique<Cat>("小猫");
std::unique_ptr<int> i = std::make_unique<int>(123);
cat->catInfo();
std::cout << "*i = " << *i << ", address of i = " << i.get() << std::endl;
std::cout << "address of cat = " << cat.get() << std::endl;
std::cout << __func__ << std::endl;
}
/* 输出
Constructor of Cat: 小猫
cat info name: 小猫
*i = 123, address of i = 0x55629f2432b0
address of cat = 0x55629f242e70
test_unique_ptr4
Destructor of Cat: 小猫
*/
3、独占指针unique_ptr与函数调用
3.1、值传递
- 值传递和赋值重载:由于是独占指针,禁止多个智能指针管理同一个地址空间
- 原因:其底层源码将拷贝构造和赋值重载函数删除禁用了,因此值传递的方式并不可行。
- 但是可以通过std::move来移交所有权,移交之后原始ptr1就为空了,再次使用会被报错
void do_pass_value(std::unique_ptr<int> ptr)
{
std::cout << "value = " << *ptr << ", address = " <<ptr.get() << std::endl;
}
void test_unique_ptr_pass_value()
{
std::unique_ptr<int> ptr1 = std::make_unique<int>(123);
// std::unique_ptr<int> ptr2 = ptr1; // 报错
// do_pass_value(ptr1); // 报错
do_pass_value(std::move(ptr1));
// std::cout << *ptr1 << std::endl; // 段错误
do_pass_value(std::make_unique<int>(100)); // ----> do_pass_value(std::move(std::make_unique<int>(100)));
/*
// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
*/
}
3.2、引用传递
- 传递引用的可以意味着调用的地方拿到的也是同一个地址
- 但是不会再调用的函数体内进行释放。
void do_pass_ref(std::unique_ptr<Cat>& ptr)
{
ptr->catInfo();
ptr->set_Name("小猫");
// 这里并不会释放ptr,因为是引用
}
void test_unique_ptr_pass_ref()
{
std::unique_ptr<Cat> ptr = std::make_unique<Cat>();
do_pass_ref(std::ref(ptr));
ptr->catInfo();
std::cout << "结束函数调用: " << __func__ << std::endl;
// 析构
}
/* 输出
cat info name: Mimi
cat info name: 小猫
结束函数调用: test_unique_ptr_pass_ref
Destructor of Cat: 小猫
*/
3.3、重置提前释放
- 传递引用的时候可以在函数体内进行reset提前释放
- 为了避免发生这种情况,可以在函数列表声明中加入const,禁止这个指针指向的修改,但是指针指向的内容可以随便改。
void do_pass_reset(/*const*/ std::unique_ptr<Cat>& ptr)
{
ptr->catInfo();
ptr->set_Name("小猫");
ptr.reset(); //const的ptr无法修改指针指向
}
void test_unique_ptr_pass_reset()
{
std::unique_ptr<Cat> ptr = std::make_unique<Cat>();
do_pass_reset(std::ref(ptr));
// ptr此时为空ptr->catInfo()报错 同时也取不到地址等
std::cout << "结束函数调用: " << __func__ << std::endl;
}
/* 输出
cat info name: Mimi
Destructor of Cat: 小猫
结束函数调用: test_unique_ptr_pass_reset
*/