智能指针的行为类似常规指针,重要的区别是它负责自动释放所指的对象。C++11标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象。
C++11提供了三种智能指针:std::shared_ptr, std::unique_ptr, std::weak_ptr,使用时需添加头文件<memory>
一、unique_ptr
unique_ptr <>是c ++ 11提供的智能指针实现之一,用于防止内存泄漏。unique_ptr对象包含一个原始指针,并负责其生命周期。当这个对象被销毁的时候,它的析构函数会删除关联的原始指针。
unique_ptr有重载的- >和*运算符。
1、初始化
直接使用new
unique_ptr<int> up1(new int()); //okay,直接初始化
unique_ptr<int> up2 = new int(); //error! 构造函数是explicit (C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显式的, 而非隐式的)
unique_ptr<int> up3(up1); //error! 不允许拷贝
与shared_ptr不同,unique_ptr拥有它所指向的对象,在某一时刻,只能有一个unique_ptr指向特定的对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。因此不允许多个unique_ptr指向同一个对象,所以不允许拷贝与赋值。
2、成员函数
unique_ptr<T> up
空的unique_ptr,可以指向类型为T的对象,默认使用delete来释放内存
up.release() up 放弃对它所指对象的控制权,并返回保存的指针,将up置为空,不会释放内存
up.reset(…) 参数可以为 空、内置指针,先将up所指对象释放,然后重置up的值.
用法请见:
https://blog.csdn.net/lijinqi1987/article/details/79005794
https://www.cnblogs.com/DswCnblog/p/5628195.html
使用实例如下:
#include <iostream> #include <memory> struct Task { int mId; Task(int id) : mId(id) { std::cout << "Task::Constructor" << std::endl; } ~Task() { std::cout << "Task::Destructor" << std::endl; } }; int main() { //空unique_ptr对象 std::unique_ptr<int> ptr1; //检查unique_ptr对象是否为空 if (!ptr1) { std::cout << "ptr1 is empty" << std::endl; } //检查unique_ptr对象是否为空 if (ptr1 == nullptr) { std::cout << "ptr1 is empty" << std::endl; } //不能通过赋值初始化创建unique_ptr对象 //std::unique_ptr<Task> taskPtr2 = new Task(); //编译错误 //通过原始指针创建unique_ptr对象 std::unique_ptr<Task> taskPtr(new Task(23)); //检查taskPtr是否为空,或者是否有关联的原始指针 if (taskPtr != nullptr) { std::cout << "taskPtr is not empty" << std::endl; } //通过unique_ptr访问内部元素 std::cout << taskPtr->mId << std::endl; std::cout << "Reset the taskPtr" << std::endl; //重置unique_ptr将删除关联的原始指针,并使unique_ptr对象为空 taskPtr.reset(); //检查taskPtr是否为空,或者是否有关联的原始指针 if (taskPtr == nullptr) { std::cout << "taskPtr is empty" << std::endl; } //通过原始指针创建unique_ptr对象 std::unique_ptr<Task> taskPtr2(new Task(55)); if (taskPtr2 != nullptr) { std::cout << "taskPtr2 is not empty" << std::endl; } //unique_ptr 对象不可复制 //taskPtr = taskPtr2; //编译错误 //unique_ptr 对象不可复制 //std::unique_ptr<Task> taskPtr3 = taskPtr2; //编译错误 { //转移所有权 std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2); //将原始指针的所有权转移给taskPtr4后,taskPtr2将为空 if (taskPtr2 == nullptr) { std::cout << "taskPtr2 is empty" << std::endl; } //taskPtr2的所有权转移给了task4 if (taskPtr4 != nullptr) { std::cout << "taskPtr4 is not empty" << std::endl; } std::cout << taskPtr4->mId << std::endl; //taskPtr4超出范围并删除关联的原始指针 } //通过原始指针创建unique_ptr对象 std::unique_ptr<Task>taskPtr5(new Task(55)); if (taskPtr5 != nullptr) { std::cout << "taskPtr5 is not empty" << std::endl; } //从原始指针释放对象的所有权 Task* ptr = taskPtr5.release(); if (taskPtr5 == nullptr) { std::cout << "taskPtr5 is empty" << std::endl; } std::cout << ptr->mId << std::endl; delete ptr; return 0; }
运行结果如下:
3、 unique_ptr特性用法——release()和reset()用法区别
//release()用法 //release()返回原来智能指针指向的指针,只负责转移控制权,不负责释放内存,常见的用法 unique_ptr<int> q(p.release()) // 此时p失去了原来的的控制权交由q,同时p指向nullptr //所以如果单独用: p.release() //则会导致p丢了控制权的同时,原来的内存得不到释放
//reset()用法 p.reset() // 释放p原来的对象,并将其置为nullptr, p = nullptr // 等同于上面一步 p.reset(q) // 注意此处q为一个内置指针,令p释放原来的内存,p新指向这个对象
主意release()只转移控制权,并不释放内存,而reset和=nullptr操作会释放原来的内存
shared_ptr会记录有多少个shared_ptr指向同一个对象,当我们拷贝或者赋值一个shared_ptr时,计数器加一,被销毁则减一,为0则释放内存(析构函数)。
每个 shared_ptr 对象在内部指向两个内存位置:
1)、指向对象的指针。
2)、用于控制引用计数数据的指针。
共享所有权如何在参考计数的帮助下工作:
1)、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
2)、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。
1、初始化
1) 构造函数初始化
std::shared_ptr<int> pointer(new int(1));
std::shared_ptr<int> pointer1 = pointer;
std::shared_ptr<std::string> ss(new std::string("AAA"));
std::shared_ptr<std::string> = std::shared_ptr<std::string>(new std::string("AAA"));
2)std::make_shared 初始化(推荐方式)
std::shared_ptr<string> p3 = std::make_shared<string>();
std::shared_ptr<string> p2 = std::make_shared<string>("hello");
//auto关键字代替std::shared_ptr,p5指向一个动态分配的空vector<string>
auto p5 = make_shared<vector<string>>();
3)reset初始化
std::shared_ptr<int> pointer = nullptr;
pointer.reset(new int(1));
2、成员函数
1) reset() 使 shared_ptr 对象取消与相关指针的关联
不带参:
pointer .reset(); //它将引用计数减少1,如果引用计数变为0,则删除指针。
带参数:
pointer.reset(new int(1)); //在这种情况下,它将在内部指向新指针,因此其引用计数将再次变为1。
使用nullptr重置:
p1 = nullptr;
2)use_count 引用计数
#include <iostream> #include <memory> using namespace std; int main() { std::shared_ptr<int> ptrA1 = std::make_shared<int>(10); std::cout << ptrA1.use_count() << std::endl; std:shared_ptr<int> ptrA2(ptrA1); std::cout << ptrA1.use_count() << std::endl; std::shared_ptr<int> ptrB1 = std::make_shared<int>(20); std::cout << ptrB1.use_count() << std::endl; ptrA2 = ptrB1; std::cout << ptrA1.use_count() << std::endl; std::cout << ptrB1.use_count() << std::endl; return 0; }
输出:1 2 1 1 2
智能指针陷阱:
https://blog.csdn.net/y1196645376/article/details/53023848
附:explicit用法
#include <iostream> using namespace std; class A { public: explicit A(int a) { cout<<"创建类成功了!"<<endl; } }; int main() { A a=10; return 0; }
上面的代码编译不成功,原因是当显式地定义了一个带一个参数的构造函数( 带explicit),必须要显示地调用构造函数,
A a(10);
如果不加 explicit的话,可以这样用
A a=10;
实际的转换过程如下:
相当于直接调用A(10)