1.访问失败:如果一块内存被多个指针引用,但其中的一个指针释放且其余的指针并不知道,这样的情况下,就发生了访问失败。
2.内存泄漏:从堆中申请了内存后不释放回去,就会引发内存泄漏。
在构造的时候分配内存,当离开作用域的时候,自动释放分配的内存,这样的话开发人员就可以从手动动态管理内存的繁杂内容中解放出来。
每种智能指针都是以类模板的方式实现的,shared_ptr
也不例外。
https://blog.csdn.net/weixin_39722329/article/details/96301534
单一所有权:所有权拥有着有义务去释放或转移所有权,同一时刻只会有一个所有权拥有者
共享所有权:和使用裸指针一样,所有权的使用者不必释放内存,引用计数器会负责释放内存,同一时刻可以有多个所有权拥有者。
1.明确资源的所属权
2.避免忘记delete,这种比较容易犯错误
3.更好的处理异常
//函数结束后shared_ptr自动释放内存
void f(){
shared_ptr sp(new int(11));
//假设抛出了异常,而且在f中未捕获
}
//函数结束后ip所指向的内存没有被释放。
void f1(){
int* ip = new int(12);
//假设delete语句前抛出了异常,而且在f中未捕获
delete ip;
}
重要性:1>>2>3
共享指针shared_ptr
是具有共享所有权语义的智能指针。 每当共享指针shared_ptr的最后一个所有者被销毁时,关联对象都将被删除(或关联资源被清除)。
std::make_shared
// make_shared分配一块int类型大小的内存,并值初始化为100(返回值是shared_ptr类型,因此可以直接赋值给sp)
shared_ptr sp = std::make_shared(100);
new 接受指针参数的智能指针构造函数是explicit的,直接初始化形式
// 错误! 不会进行隐式转换,类型不符合
shared_ptr sp1 = new int(100);
// 正确,直接初始化调用构造函数
shared_ptr sp2(new int(100000));
p.get()
swap(p,q)
shared_ptr p(q)
p = q
p.use_count()
shared_ptr p(q,d)
p.reset()
p.reset(p)
p.reset(p,d)
shared_ptr为什么没有release()
赋值(增加)
auto sp = make_shared(1024); // sp的引用计数为1
#include
#include
using namespace std;
// compile:g++ test.cpp -o a.exe -std=c++11
int main()
{
{
auto sp1 = make_shared<string>("obj1");
auto sp2(sp1);
auto sp3 = make_shared<string>("obj2");
cout << "before sp2->use_count() = " << sp2.use_count() << '\n';
cout << "before sp3->use_count() = " << sp3.use_count() << '\n';
sp1 = sp3; // 该操作会减少sp2的引用计数,增加sp3的引用计数
cout << "after sp2->use_count() = " << sp2.use_count() << '\n';
cout << "after sp3->use_count() = " << sp3.use_count() << '\n';
}
return 0;
}
该操作会减少sp2的引用计数,增加sp3的引用计数。(sp1、sp2指向对象obj1,sp3指向对象obj2,那么赋值之后,sp1也会指向obj2,那就是说指向obj1的就少了,指向obj2的就会多。)
拷贝(增加)
auto sp2 = make_shared(1024);
auto sp1(sp2);
该操作会使得sp1和sp2都指向同一个对象。
传参(拷贝)(增加)
而关于拷贝比较容易忽略的就是作为参数传入函数:
auto sp2 = make_shared(1024);
func(sp2); // func的执行会增加其引用计数
reset(减少)
释放sp指向的对象( 而如果sp是唯一指向该对象的,则该对象被销毁 )
sp.reset()
参考:https://en.cppreference.com/w/cpp/memory/shared_ptr
#include
#include
// g++ test.cpp -o a.exe -std=c++11
class Base
{
public:
Base() { std::cout << " Base::Base()\n"; }
~Base() { std::cout << " Base::~Base()\n"; }
};
class Derived: public Base
{
public:
Derived() { std::cout << " Derived::Derived()\n"; }
~Derived() { std::cout << " Derived::~Derived()\n"; }
};
void test(std::shared_ptr p)增加引用计数
{
std::cout << "local pointer in a function:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
std::shared_ptr lp = p;
std::cout << "local pointer in a function:\n"
<< " lp.get() = " << lp.get()
<< ", lp.use_count() = " << lp.use_count() << '\n';
}//销毁ptr,减少引用计数
int main()
{
std::shared_ptr p = std::make_shared();
std::cout << "func before:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
test(p);
std::cout << "func after:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
}
运行结果
Base::Base()
Derived::Derived()
func before:
p.get() = 0x1028c30, p.use_count() = 1
local pointer in a function:
p.get() = 0x1028c30, p.use_count() = 2
local pointer in a function:
lp.get() = 0x1028c30, lp.use_count() = 3
func after:
p.get() = 0x1028c30, p.use_count() = 1
Derived::~Derived()
Base::~Base()
重置共享指针,减少计数
#include
#include
// 编译: g++ test.cpp -o a.exe -std=c++11
using namespace std;
class A {
public:
int i ;
A() { cout << "construct\n"; }
~A() { cout << "delete "<< i <<"\n"; }
};
int main()
{
// 共享指针a,b,c都指向堆内存new A的位置
shared_ptr a( new A);
shared_ptr b(a);
shared_ptr c(b);
shared_ptr d(new A);
a->i = 10;
cout << a.use_count() << endl;
cout << b.use_count() << endl;
d->i = 30;
// 错:不要用p.get()的返回值为shared_ptr赋值,因为返回的是裸指针,很容易被共享指针重复释放,造成错误
//a.reset(d.get())
// 令a释放指向的空间 A
//a.reset();
// 令a释放指向的空间 A,指向新空间
a.reset(new A);
a->i = 20;
cout << b.use_count() << endl;
cout << "end" <
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器,原理是:当删除器的指针Deleter
传给shared_ptr/unique_ptr时,shared_ptr/unique_ptr不会使用默认的delete val
来释放其管理的资源,而是使用Deleter(val)
来释放资源,这样就调用了Deleter来释放管理的资源。
1.普通删除函数定义类似于:
void Deleter(T *val){
// 其他代码
// 释放val的内存
delete val;
// 或者(如果val是数组)
delete[] val;
}
#include
#include
#include
// 编译: g++ test.cpp -o a.exe -std=c++11
using namespace std;
class Connection{
public:
string _name;
explicit Connection(string name):_name(name){
}
string get_name() const {
return _name;
}
};
void close(Connection* connection){
cout << string("关闭") + connection->get_name() + "管理的连接中..." << endl;
// 关闭连接的代码
// .....
cout << "关闭完成。" << endl;
}
// 函数式删除器
void Deleter(Connection *connection){
close(connection);
delete connection;
}
int main(){
// 新建管理连接Connection的智能指针
shared_ptr sp(new Connection("shared_ptr"), Deleter);
sp->_name = "hello";
}
对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。释放规则可以使用 C++11 标准中提供的 default_delete 模板类,我们也可以自定义释放规则:
//1.指定 default_delete 作为释放规则
std::shared_ptr p6(new int[10], std::default_delete());
//2.自定义释放规则
void deleteInt(int*p) {
delete []p;
}
//初始化智能指针,并自定义释放规则
std::shared_ptr p7(new int[10], deleteInt);
//3.lambda方式构造和释放
std::shared_ptr p7(new int[10], [](int* p) {delete[]p; });
1.不要与裸指针混用
错误场景1:
int *x(new int(10));
shared_ptr sp1(x);
shared_ptr sp2(x);
//x随时可能变成空悬指针而无从知晓
错误场景2:
int *x(new int(10));
//创建了一个指向x指针所指内存的共享指针,引用计数为1,是引用这块内存的唯一共享指针
func(shared_ptr (x));
//离开函数即离开共享指针的作用域,这块内存即被删除
2.谨慎使用p.get()的返回值
shared_ptr sp1(new int(10));
shared_ptr sp2(sp1), sp3;
sp3 = sp1;
//一个典型的错误用法
shared_ptr sp4(sp1.get());
cout << sp1.use_count() << " "
<< sp2.use_count() << " "
<< sp3.use_count() << " "
<< sp4.use_count() << endl;
//输出:
3
3
3
1(独立)
sp1,sp2,sp3是相互关联的共享指针,共同控制所指内存的生存期,sp4虽然指向同样的内存,却是与sp1,sp2,sp3独立的,sp4按自己的引用计数来关联内存的释放。
两个unique_ptr不能指向同一个对象,不能进行复制操作,只能进行移动操作。
与shared_ptr不同,unique_ptr没有定义类似make_shared的操作,因此只可以使用new来分配内存,并且由于unique_ptr不可拷贝和赋值,初始化unique_ptr必须使用直接初始化的方式。
unique_ptr up1(new int()); // okay:直接初始化
std::unique_ptr p4(new int);
std::unique_ptr p5(std::move(p4)); // okay:调用移动构造函数,p5 将获取 p4 所指堆空间的所有权,而 p4 将变成空指针(nullptr)
unique_ptr up2 = new int(); // error! 避免隐式转换
unique_ptr up3(up1); // error! 不允许拷贝
up.release()
up放弃对它所指对象的控制权,并返回保存的指针,将up置为空,不会释放内存
up.reset()
参数可以为空,内置指针,先将up所指对象释放,然后重置up的值
前面说了unique_ptr不可拷贝和赋值,那要怎样传递unique_ptr参数和返回unique_ptr呢? 事实上不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr
// 从函数返回一个unique_ptr
unique_ptr func1(int a)
{
return unique_ptr (new int(a));
}
// 返回一个局部对象的拷贝
unique_ptr func2(int a)
{
unique_ptr up(new int(a));
return up;
}
或者是引用
void func1(unique_ptr &up){
cout<<*up< func2(unique_ptr up){
cout<<*up< up(new int(10));
// 传引用,不拷贝,不涉及所有权的转移
func1(up);
// 暂时转移所有权,函数结束时返回拷贝,重新收回所有权
up = func2(unique_ptr (up.release()));
// 如果不用up重新接受func2的返回值,这块内存就泄漏了
释放方法:注意!注意!注意!这里的释放并不会摧毁其指向的对象,而且将其指向的对象释放出去。
#include
#include
//编译:g++ test.cpp -o a.exe -std=c++11
int main () {
std::unique_ptr auto_pointer(new int);
int * manual_pointer;
*auto_pointer=10;
manual_pointer = auto_pointer.release();
// (auto_pointer is now empty)
std::cout << "manual_pointer points to " << *manual_pointer << '\n';
delete manual_pointer;
return 0;
}
执行结果为:
manual_pointer points to 10
重置方法,销毁由该智能指针管理的任何可能存在的对象,该智能指针被指为空
#include
#include
//编译:g++ test.cpp -o a.exe -std=c++11
int main () {
std::unique_ptr up; // empty
up.reset (new int); // takes ownership of pointer
*up=5;
std::cout << *up << '\n';
up.reset (new int); // deletes managed object, acquires new pointer
*up=10;
std::cout << *up << '\n';
up.reset(); // deletes managed object
return 0;
}
执行结果:
5
10
#include
#include
#include
// 编译: g++ test.cpp -o a.exe -std=c++11
using namespace std;
class Connection{
public:
string _name;
explicit Connection(string name):_name(name){
}
string get_name() const {
return _name;
}
};
void close(Connection* connection){
cout << string("关闭") + connection->get_name() + "管理的连接中..." << endl;
// 关闭连接的代码
// .....
cout << "关闭完成。" << endl;
}
// 函数式删除器
void Deleter(Connection *connection){
close(connection);
delete connection;
}
int main(){
// 新建管理连接Connection的智能指针
unique_ptr up(new Connection("unique_ptr"), Deleter);
up->_name = "hello";
}
错误做法
int *x(new int());
unique_ptr up1,up2;
// 会使up1 up2指向同一个内存
up1.reset(x);
up2.reset(x);
unique_ptr不允许两个独占指针指向同一个对象,在没有裸指针的情况下,我们只能用release获取内存的地址,同时放弃对对象的所有权,这样就有效避免了多个独占指针同时指向一个对象。
正确做法
unique_ptr up1(new int()); // okay,直接初始化
unique_ptr up2;
up2.reset(up1.release());
不是说任何地方都要使用智能指针,比如说你想传递一个对象到一个函数里,那你就可以使用引用或者普通指针(raw ptr), 这里的引用和普通指针体现的是没有所属权(ownership),也就是说函数本身不负责这个对象的生命周期。只有需要体现所属权(ownership)的创立和变动的时候,采用智能指针。参考
选择条件:
在使用智能指针的时候,优先选用unique_ptr,原因如下:
1.语义简单,即当你不确定使用的指针是不是被分享所有权的时候,默认选unique_ptr独占式所有权,当确定要被分享的时候可以转换成shared_ptr;
2.unique_ptr效率比shared_ptr高,不需要维护引用计数和背后的控制块;
3.unique_ptr用起来更顺畅,选择性更多,可以转换成shared_ptr和通过get和release定制化智能指针(custom smart pointer)。
如果有多个指针指向同一对象的话,你应该使用shared_ptr;
如果一个对象只需要一个智能指针,那你应该是用unique_ptr,它非常适合于返回值类型为unique_ptr的函数
因为不能被拷贝,所以传递裸指针或者引用或者外部不需要了直接转移,但是要注意不能传递值(拷贝)
// 裸指针
#include
#include
void test(int *p)
{
*p = 10;
}
int main()
{
std::unique_ptr up(new int(42));
test(up.get());//传入裸指针作为参数
std::cout<<*up<
#include
void test(std::unique_ptr &p)
{
*p = 10;
}
int main()
{
std::unique_ptr up(new int(42));
test(up);
std::cout<<*up<
#include
void test(std::unique_ptr p)
{
*p = 10;
}
int main()
{
std::unique_ptr up(new int(42));
test(std::unique_ptr(up.release()));
//test(std::move(up));//这种方式也可以
return 0;
}
返回可以用unique_ptr(伪代码),这里可以理解为返回值是拷贝了指向的地方,相当于std::move,获取唯一的所有权
unique_ptr make_init(int n)
{
return unique_ptr (new int(n));
}
int main()
{
···
vector> vp(size);
for(int i = 0; i < vp.size(); i++)
vp[i] = make_init(rand() % 1000)
···
}
#include
#include
void func0(std::shared_ptr sp)
{
std::cout<<"fun0:"< sp)
{
std::cout<<"fun1:"< &sp)
{
std::cout<<"fun1:"<(1024);
func0(sp); // 拷贝方式(这种方式unique不可以)
func1(sp); // 拷贝方式(这种方式unique不可以)
func2(sp); // 引用方式
return 0;
}
这里建议传参使用引用,免拷贝。
主要参考:https://www.yanbinghu.com/categories/Cpp/
C++11智能指针: https://www.jianshu.com/p/e4919f1c3a28
C++11 shared_ptr智能指针:http://c.biancheng.net/view/7898.html
shared_ptr官方讲解:http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/
unique_ptr官方讲解:http://www.cplusplus.com/reference/memory/unique_ptr/unique_ptr/
智能指针的选择:https://blog.csdn.net/qq_22533607/article/details/82318595