一、std::shared_ptr使用场景
shared_ptr<int> create(int value)
{
return make_shared<int>(value); //返回一个shared_ptr
}
shared_ptr<int> myfunc(int value)
{
shared_ptr<int> ptmp = create(value);
return ptmp; //系统会根据ptmp这个局部变量产生一个临时的shared_ptr对象往回返
}
void myfunc_void(int value)
{
shared_ptr<int> ptmp = create(value);
return; //离开作用域后,ptmp会被自动释放,它所指向的内存也会被自动释放
}
int main()
{
myfunc_void(12); //如果这块不用shared_ptr变量来接收myfunc返回的结果,那么从myfunc返回的shared_ptr就会被销毁。
//所指向的对象也会被销毁。
auto p1 = myfunc(10); //我们用了一个变量来接myfunc的返回值,那么myfunc返回的shared_ptr就不会被销毁,
//它所指向的对象也不会被销毁。
return 0;
}
二、std::shared_ptr使用陷阱分析
<1>慎用裸指针
void proc(shared_ptr<int> value)
{
return;
}
int main()
{
int* p = new int(100); //裸指针
cout << p << endl;
//proc(p); //语法错,int *p不能转换成shared_ptr<int>
//shared_ptr<int> p2(p);
//proc(p2);
//*p = 45; //有两个shared_ptr,是没有问题的
proc(shared_ptr<int>(p)); // 参数是个临时的shared_ptr,用一个裸指针显式的构造;
*p = 45; // 出现潜在的不可预料的问题,因为p指向的内存已经被释放
cout << p << endl;
return 0;
}
把一个普通裸指针绑定到一个 shared_ptr 上之后,那么内存管理的责任就交给 shared_ptr了,这个时候你就不应该再用裸指针(内置指针)来访问 shared_ptr 所指向的内存了。
void proc(shared_ptr<int> value)
{
return;
}
int main()
{
shared_ptr<int> p3(new int(100));
proc(p3);
*p3 = 45;
return 0;
}
通过内存查看窗口显示地址在函数退出时已经释放,再次使用是不安全的。
绝对要记住不要用裸指针初始化多个shared_ptr
int main()
{
int* p = new int(100); //裸指针
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); //p1和p2无关联(p1和p2的每个强引用计数都为1了),会导致p1和p2所指向的内存释放两次,产生异常。
shared_ptr<int> p1(new int);
shared_ptr<int> p2(p1); //这种写法就是OK的,p1和p2指向同一个内存地址并且两者是互通的(用的是同一个控制块);
return 0;
}
<2>慎用get()返回的指针
返回智能指针指向的对象所对应的裸指针(有些函数接口可能只能使用裸指针)
get返回的指针不能delete,否则会异常。
int main()
{
shared_ptr<int> myp(new int(100));
int* pp = myp.get();
delete pp; //不可以删除,会导致异常
return 0;
}
不能将其他智能指针绑到get返回的指针上!!!
不能将get返回的指针绑到其他智能指针上!!!
int main()
{
shared_ptr<int> myp(new int(100));
int* pp = myp.get(); //这个指针千万不能随意释放,否则myp就没办法正常管理该指针了
{
shared_ptr<int> myp2;
myp2 = shared_ptr<int>(pp);
shared_ptr<int> myp2(pp); //现在myp和myp2引用计数都为1,但是一旦跳出这个程序块,离开上边这个myp2的作用域,导致myp指向的内存被释放了。
}
*myp = 65; //该内存已经被释放,这样的赋值会导致不可预料的后果
return 0;
}
结论:永远不要用get得到的一个指针初始化另一个智能指针或者给另外一个智能指针赋值。
<3>不要用类对象指针( this )作为 shared_ptr 返回,改用 enable_shared_form_this。
class CT
{
public:
shared_ptr<CT> getself()
{
return shared_ptr<CT>(this); // 用裸指针初始化了多个shared_ptr的感觉
}
};
int main()
{
shared_ptr<CT> pct1(new CT);
shared_ptr<CT> pct2 = pct1; //这是两个强引用
shared_ptr<CT> pct2 = pct1->getself(); //问题出现
return 0;
}
用c++标准库里面的类模板:enable_shared_from_this。
现在在外面创建CT对象的智能指针以及通过CT 对象返回的 this 智能指针都是安全的
这个 enbale_shared_from_this 中有个弱指针 weak_ptr,这个弱指针能够监视 this。
在我们调用 shared_from_this() 这个方法时,这个方法内部实际上是调用这个 weak_ptr 的lock() 方法。
大家都知道 lock() 方法会让 shared_ptr 引用计数+1,同时返回这个 shared_ptr,这个就是工作原理。
<4>避免循环引用:能够导致内存泄漏 ???析构函数执行顺序是依照什么来的???
class CB; //声明一下CB
class CA
{
public:
shared_ptr<CB> m_pbs;
~CA()
{
int test;
test = 1;
}
};
class CB
{
public:
//shared_ptr<CA> m_pas;
weak_ptr<CA> m_pas; //把这里变成弱引用
~CB()
{
int test;
test = 1;
}
};
int main()
{
shared_ptr<CA> pca(new CA);
shared_ptr<CB> pcb(new CB);
pca->m_pbs = pcb; //等价于指向CB对象的有两个强引用
pcb->m_pas = pca; //1.等价于指向CA对象的有两个强引用 2.因为m_pas是弱引用,这里指向CA的对象只有一个强引用
return 0;
}
离开作用域之后,pca引用计数从1变成0会释放 CA 对象(CA的析构函数被执行)。
CA 的析构函数被执行了(表示对象即将被释放),导致CA内的 m_pbs 引用计数减1;
也就是指向CB的对象引用计数减1,超出pcb作用域指向CB的引用计数也会减1,最终,会有一个时刻,指向CB对象的强引用计数会从1减少到0,导致CB对象被释放。CA先析构,CB后析构。
三:性能说明
<1>尺寸问题
shared_ptr的尺寸是裸指针的两倍, weak_ptr的尺寸也是裸指针的两倍。
int main()
{
char* p;
int ilenp = sizeof(p); //4字节
shared_ptr<string> p1;
int ilensp = sizeof(p1); //8字节,包含两个裸指针的
return 0;
}
(a)第一个裸指针指向这个智能指针所指向的对象;
(b)第二个裸指针指向一个很大的数据结构(控制块),这个控制块里边有:
(b.1) 所指对象的强引用计数: shared_ptr
(b.2) 所指对象的弱引用计数: weak_ptr
(b.3) 其他数据,比如删除器指针,内存分配器等等
这个控制块是由第一个指向某个对象的shared_ptr来创建的
控制块创建时机
(a)make_shared:分配并初始化一个对象,返回指向此对象的 shared_ptr,所以,这个make_shared 它总是能够创建一个控制块
shared_ptr p2 = make_shared(100);
(b)用裸指针来创建一个 shared_ptr 对象时
int* pi = new int();
shared_ptr p1(pi);
shared_ptr p2(pi); // 不允许,否则会产生多个控制块,也就是多个引用计数(每个都是1),析构时析构多次,导致异常
shared_ptr p2(new int(100));
<2>移动语义
shared_ptr p1(new int(100));
shared_ptr p2(std::move(p1)); // 移动语义,移动构造一个新的智能指针对象p2,p1就不在指向该对象了(变为空),引用计数依旧是1
shared_ptr p3;
p3 = std::move(p2); //移动赋值,p2指向空,p3指向该对象,整个对象引用计数仍旧为1;
移动肯定比复制快,复制你要增加引用计数,移动不需要;
移动构造函数快过复制构造函数,移动赋值运算符快过拷贝赋值运算符
四:补充说明和使用建议
<1>掌握了绝大部分shared_ptr用法, 小部分没讲解,靠大家摸索
分配器解决内存分配问题
shared_ptr p((new int()), mydeleter(), mymallocator());
<2>谨慎使用,凡是老师没讲到过的用法,
new shared_ptr, memcpy() 没有提及的奇怪用法,不要轻易尝试
<3>优先使用make_shared(),不能定义自己的删除器 ???
shared_ptr ps(new string(“I Love China”)); // 分配两次内存
auto p2 = make_shared(“I Love China”); // 分配一次内存