[c++11]智能指针学习笔记

news/2024/2/29 3:27:53

C#Javapythongo等语言中都有垃圾自动回收机制,在对象失去引用的时候自动回收,而且基本上没有指针的概念,而C++语言不一样,C++充分信任程序员,让程序员自己去分配和管理堆内存,如果管理的不好,就会很容易的发生内存泄漏问题,而C++11增加了智能指针(Smart Pointer)。主要分为shared_ptrunique_ptrweak_ptr三种,使用时需要引用头文件<memory>c++98中还有auto_ptr,基本被淘汰了,不推荐使用。而c++11shared_ptrweak_ptr都是参考的boost库中实现的。

我的简书链接

原始指针

c语言中最常使用的是malloc()函数分配内存,free()函数释放内存,而c++中对应的是newdelete关键字。malloc()只是分配了内存,而new则更进一步,不仅分配了内存,还调用了构造函数进行初始化。使用示例:

int main()
{// malloc返回值是 void*int* argC = (int*)malloc(sizeof(int));free(argC);char*c = (char*)malloc(100);free(c);char *age = new int(25); //做了两件事情 1.分配内存 2.初始化int* height = new int(160);delete height;delete age;char* arr = new int[100];delete[] arr;/*delete数组需要使用delete[],事实上,c++原始支持的数据结构组成的数组不需要[]也可以,但 自定义的数据类型组成的数组必须使用delete[]*/
}

newdelete必须成对出现,有时候是不小心忘记了delete,有时候则是很难判断在这个地方自己是不是该delete,这个和资源的生命周期有关,这个资源是属于我这个类管理的还是由另外一个类管理的,如果是我管理的,就由我来delete,由别人管理的就由别人delete,我就算析构了也不影响该资源的生命周期。例如:

// 情况1: 需要自己delete
const char* getName() {char *valueGroup = new char[1000];// do somethingreturn valueGroup;
}
// 情况2: 不需要自己delete
const char* getName2() {static char valueGroup[1000];// do somethingreturn valueGroup;
}

只通过函数签名来看,这两个函数没有什么区别,但是由于实现的不同,有时候需要自己管理内存,有时候不需要,这个时候就需要看文档说明了。这就是使用一个"裸"指针不好的地方。

一点改进是,如果需要自己管理内存的话,最好显示的将自己的资源传递进去,这样的话,就能知道是该资源确实应该由自己来管理。

char *getName(char* v, size_t bufferSize) {//do somethingreturn v;
}

上面还是小问题,自己小心一点,再仔细看看文档,还是有机会避免这些情况的。但是在c++引入异常的概念之后,程序的控制流就发生了根本性的改变,在写了delete的时候还是有可能发生内存泄漏。如下例:

void badThing(){throw 1;// 抛出一个异常
}void test() {char* a = new char[1000];badThing();// do somethingdelete[] a;
}
int main() {try {test();}catch (int i){cout << "error happened " << i << endl;}
}

上面的newdelete是成对出现的,但是程序在中间的时候抛出了异常,由于没有立即捕获,程序从这里退出了,并没有执行到delete,内存泄漏还是发生了。

C++中的构造函数和析构函数十分强大,可以使用构造和析构解决这种问题,比如:

class SafeIntPointer {
public:explicit SafeIntPointer(int v) : m_value(new int(v)) { }~SafeIntPointer() {delete m_value;cout << "~SafeIntPointer" << endl;}int get() { return *m_value; }
private:int* m_value;
};void badThing(){throw 1;// 抛出一个异常
}void test() {SafeIntPointer a(5);badThing();
}
int main() {try {test();}catch (int i){cout << "error happened " << i << endl;}
}// 结果
// ~SafeIntPointer
// error happened 1

可以看到,就算发生了异常,也能够保证析构函数成功执行!这里的例子是这个资源只有一个人使用,我不用了就将它释放掉。但还有种情况,一份资源被很多人共同使用,要等到所有人都不再使用的时候才能释放掉,对于这种问题,就需要对上面的SafeIntPointer增加一个引用计数,如下:

class SafeIntPointer {
public:explicit SafeIntPointer(int v) : m_value(new int(v)), m_used(new int(1)) { }~SafeIntPointer() {cout << "~SafeIntPointer" << endl;(*m_used) --; //引用计数减1if(*m_used <= 0){delete m_used;delete m_value;cout << "real delete resources" << endl;}}SafeIntPointer(const SafeIntPointer& other) {m_used = other.m_used;m_value = other.m_value;(*m_used)++; //引用计数加1}SafeIntPointer& operator= (const SafeIntPointer& other) {if (this == &other) // 避免自我赋值!!return *this;m_used = other.m_used;m_value = other.m_value;(*m_used)++; //引用计数加1return *this;}int get() { return *m_value; }int getRefCount() {return *m_used;}private:int* m_used;int* m_value;
};int main() {SafeIntPointer a(5);cout << "ref count = " << a.getRefCount() << endl;SafeIntPointer b = a;cout << "ref count = " << a.getRefCount() << endl;SafeIntPointer c = b;cout << "ref count = " << a.getRefCount() << endl;
}/*
ref count = 1
ref count = 2
ref count = 3
~SafeIntPointer
~SafeIntPointer
~SafeIntPointer
real delete resources
*/

可以看到每一次赋值,引用计数都加一,最后每次析构一次后引用计数减一,知道引用计数为0,才真正释放资源。要写出一个正确的管理资源的包装类还是蛮难的,比如上面那个上面例子就不是线程安全的,只能属于一个玩具,在实际工程中简直没法用。而到了C++11,终于提供了一个共享的智能指针解决这个问题。

shared_ptr共享的智能指针

shared_ptr的基本使用

shared_ptr的基本使用很简单,看几个例子就明白了:

#include <iostream>
#include <memory>class Object {
public:Object(int id) : m_id(id) {std::cout << "init obj " << std::endl;}~Object() {std::cout << "bye bye" << m_id << std::endl;}int id() const {return m_id;}
private:int m_id;
};// 取个别名 让写起来更方便
typedef std::shared_ptr<Object> ObjectPtr;void print(Object* obj) {std::cout << "in print(Object* obj) :  " << std::endl;
//    std::cout << "ref count is " << obj.use_count() << std::endl;
}void print(Object obj) {std::cout << "in print(Object obj) :  " << std::endl;
//    std::cout << "ref count is " << obj.use_count() << std::endl;
}void print(ObjectPtr obj) {std::cout << "in print(ObjectPtr obj) :  ";std::cout << "ref count is " << obj.use_count() << std::endl;
}void printRef(const ObjectPtr& obj) {std::cout << "in print(const ObjectPtr& obj) :  ";std::cout << "ref count is " << obj.use_count() << std::endl;
}int main() {// 创建一个智能指针,管理的资源就是 new出来的Object(1)//ObjectPtr obj=new Object(1); //不能将一个原始指针直接赋值给一个智能指针ObjectPtr obj(new Object(1)); // 正确std::cout << "ref count is " << obj.use_count() << std::endl; // 1ObjectPtr obj2(obj);std::cout << "ref count is " << obj.use_count() << std::endl; // 2std::cout << "ref count is " << obj2.use_count() << std::endl; // 2 obj和obj2管理的资源是一样的ObjectPtr obj3 = obj2;std::cout << "ref count is " << obj.use_count() << std::endl; // 3obj2.reset(); // obj2不再管理之前的资源,资源的引用计数会减1// 或者可以写成 obj2 = nullptr;std::cout << "ref count is " << obj.use_count() << std::endl; // 2ObjectPtr obj4; //obj4 开始没有管理资源// 将管理的资源 相互交换// 交换后 obj3没有再管理资源, obj4管理obj3之前管理的资源// 或者写成 std::swap(obj3, obj4);obj3.swap(obj4);std::cout << "ref count is " << obj.use_count() << std::endl; // 还是 2// 还可以从智能指针中获取原始指针auto p = obj.get(); // auto = Object*// 需要判断这个obj是否确实管理着资源, 可能为 nullptrif( p ) {std::cout << "p->id() is " << p->id() << std::endl;std::cout << "(*p).id() is " << (*p).id() << std::endl;}// 智能指针也可以像普通指针一样使用// 重载了 operator boolif( obj ) {std::cout << "obj->id() is " << obj->id() << std::endl; // 重载了operator ->std::cout << "(*obj).id() is " << (*obj).id() << std::endl;// 重载了operator *}// obj.use_count()可以判断当前有多少智能指针在管理资源// 如果判断是不是只有一个人在管理这个资源, 用unique()函数更加高效// unique() 等价于 obj.use_count() == 1obj4 = nullptr; // obj4不在管理,这个时候的引用计数变成了 1std::cout << "ref count is " << obj.use_count() << std::endl; // 1if( obj.unique() )std::cout << "only one hold ptr "<<  std::endl;elsestd::cout << "not noly one hold ptr" << std::endl; //其实也有可能没有人再管理// 将智能指针当作参数传递给函数时// 如果是值传递, 智能指针发生一次拷贝,// 在函数内部时智能指针的引用计数会 + 1// 离开函数作用域时, 智能指针会析构, 引用计数会 - 1print(obj);// 如果传递的是引用, 对引用计数没影响 而且工作量比较小(没有拷贝)// 推荐使用引用方式传递, 传值的方式也有用处,比如多线程时printRef(obj);// 还可以不传递智能指针, 传递原生类型print(*obj); //传Object类型的时候,离开函数的时候参数obj会发生一次析构print(obj.get());}
/*
init obj
ref count is 1
ref count is 2
ref count is 2
ref count is 3
ref count is 2
ref count is 2
p->id() is 1
(*p).id() is 1
obj->id() is 1
(*obj).id() is 1
ref count is 1
only one hold ptr
in print(ObjectPtr obj) :  ref count is 2
in print(const ObjectPtr& obj) :  ref count is 1
in print(Object obj) :
bye bye1    //这个是在调用print(Object obj)时,局部变量析构时打印的
in print(Object* obj) :
bye bye1 //这个是在资源在没有人引用的时候,执行析构函数产生的
*/

给shared_ptr指定删除器

大部分用法都基本上在上面的例子中体现出来了,当没有人引用这个资源的时候,智能指针的默认行为是调用 delete销毁这个资源,而我们也可以人为指定这个步骤,因为有些资源不一定是new出来的,所以不应该使用默认的delete行为,还有一个情况是,在用智能指针管理动态数组的时候,需要自己指定删除器函数。

#include <iostream>
#include <memory>class Object {
public:Object(int id) : m_id(id) {std::cout << "init obj " << std::endl;}~Object() {std::cout << "bye bye" << m_id << std::endl;}int id() const {return m_id;}
private:int m_id;
};// 让写起来更方便
typedef std::shared_ptr<Object> ObjectPtr;void deleterOfObject(Object* obj) {if ( obj ) {std:: cout << "delete obj " << obj->id() << std::endl;delete obj;}
}void useDeleter() {//指定删除动作  使用外面定义的函数ObjectPtr obj(new Object(2), deleterOfObject);ObjectPtr obj2 = obj;// obj 和 obj2 会在离开这个函数的时候析构,于是,就调用了 deleterOfObject//管理数组  使用匿名函数当作删除函数std::shared_ptr<int> p(new int[10], [](int* p){std::cout << "delete[] p" << std::endl;delete[] p; //需要使用delete[]});// vector<> 没必要使用智能指针, 不用new 和 delete... 内部已经管理了
}int main() {useDeleter();
}/*
init obj
delete[] p  //注意析构和构造的顺序是相反的
delete obj 2
bye bye2
*/

shared_ptr主要就是利用变量出了作用域之后析构函数一定能被调用到,哪怕是出现了异常。

不要用一个原始的指针初始化多个shared_ptr

例如下面的例子:

#include <iostream>
#include <memory>class Object {
public:Object(int id) : m_id(id) {std::cout << "init obj " << std::endl;}~Object() {std::cout << "bye bye " << m_id << std::endl;}int id() const {return m_id;}
private:int m_id;
};// 让写起来更方便
typedef std::shared_ptr<Object> ObjectPtr;int main() {Object *obj = new Object(2);ObjectPtr p1(obj);ObjectPtr p2(obj);std::cout << p1.use_count() << "  " << p2.use_count() << std::endl;std::cout << "finished" << std::endl;
}/*
init obj
1  1
finished
bye bye 2
bye bye 203200  //m_id成为了随机数
*/

可以发现,虽然是用的同一个指针初始化了两个shared_ptr,但是这两个shared_ptr没有关联,它们的引用计数都是1,然后问题就发生了,p2先析构,于是引用计数变为了0,就开始删除它管理的资源obj,于是obj就被析构了,这是还算正常,接着析构p1,引用计算也变成了0,它也开始删除自己管理的资源obj,相当于多次delete了同一个对象,m_id成为了随机数,这还算好的情况,如果Object内部还有指针,或者obj的地址被其他变量占据了,delete掉这块内存就会发生严重的错误!而且不好发现原因。

将this指针正确的传递给shared_ptr

其实就是由于上面的原因,我们不可能传递this指针给shared_ptr,因为用同一个指针初始化两个shared_ptr,它们之间并没有关联,如下面的例子:

#include <iostream>
#include <memory>class Y
{
public:std::shared_ptr<Y> f(){return std::shared_ptr<Y>(this);}
};int main()
{std::shared_ptr<Y> p1(new Y());std::shared_ptr<Y> p2 = p1->f(); // p2是由this构造共享智能指针std::cout << p1.use_count() << "  " << p2.use_count() << std::endl; // 1  1
}

从上面的例子可以看出,返回由this构造的shared_ptr并没有用,返回还可能造成严重错误(由于可能多次delete)!解决办法是继承std::enable_shared_from_this<Y>,然后使用shared_from_this()构造shared_ptr

#include <iostream>
#include <memory>class Y : public std::enable_shared_from_this<Y>
{
public:std::shared_ptr<Y> f(){return shared_from_this();}
};int main()
{std::shared_ptr<Y> p1(new Y());std::shared_ptr<Y> p2 = p1->f(); // p2是由p1的thiss构造共享智能指针std::cout << p1.use_count() << " " << p2.use_count() << std::endl; // 2  2std::shared_ptr<Y> p3(new Y());std::shared_ptr<Y> p4 = p3->f(); // p4是由p3的this构造的构造共享智能指针std::cout << p1.use_count() << " " << p2.use_count() << " "<< p3.use_count() << " " << p4.use_count() << std::endl; // 2 2 2 2
}

可以发现引用计数确实增加了。并且由p1得到的shared_from_this()增加的就是p1的引用计数,p3得到的shared_from_this()增加的就是p3的引用计数,这和this的含义是一样的。所以我们在类内部需要传递this指针给shared_ptr时,需要继承自std::enable_shared_from_this<T>,并且使用shared_from_this()替代this。而shared_from_this()就是借助了weak_ptr。原理在后面再讲。

shared_ptr的正确构造方式

其实上面使用的智能指针构造方式有一点点问题,ObjectPtr obj(new Object(1));这一个语句其实调用了两次new,一次是new Object(1),另一次是构造内部的引用计数变量的时候,那有没有办法只掉用一次new呢,答案就是使用make_shared<T>()模板函数,它将资源和引用计数变量一起new出来,例如:

#include <iostream>
#include <memory>
#include <cassert>
class Object {
public:Object(int id) : m_id(id) {std::cout << "init obj " << std::endl;}~Object() {std::cout << "bye bye " << m_id << std::endl;}int id() const {return m_id;}
private:int m_id;
};// 让写起来更方便
typedef std::shared_ptr<Object> ObjectPtr;int main() {// 和 ObjectPtr obj(new Object(2)); 一样// 但是只调用了一次newObjectPtr obj = std::make_shared<Object>(2);
}

然而,这个函数也有失效的时候,如果管理的资源对象的构造函数是私有的他就没有办法了。

weak_ptr弱引用的智能指针

循环引用问题的引出

在有些情况下,shared_ptr也会遇见很尴尬、不能处理的情况,那就是循环引用,看下面的例子:

#include <iostream>
#include <memory>class Parent;  //Parent类的前置声明typedef std::shared_ptr<Parent> ParentPtr;class Child {
public:ParentPtr father;~Child() {std::cout << "bye child" << std::endl;}
};typedef std::shared_ptr<Child> ChildPtr;class Parent {
public:ChildPtr son;~Parent() {std::cout << "bye parent" << std::endl;}
};void testParentAndChild() {ParentPtr p(new Parent());  // 1  资源AChildPtr c(new Child());  // 2   资源Bp->son = c;     // 3      c.use_count() == 2 and p.use_count() == 1 c->father = p;    //  4   c.use_count() == 2 and p.use_count() == 2
}int main() {testParentAndChild();std::cout << "finished" << std::endl;
}/*
// 没有调用Parent 和 Child 的析构函数
finished
*/

很惊讶的发现,用了shared_ptr管理资源,资源最后还是没有释放!内存泄漏还是发生了。

分析:

  1. 执行编号1的语句时,构造了一个共享智能指针p,称呼它管理的资源叫做资源Anew Parent()产生的对象)吧, 语句2构造了一个共享智能指针c,管理资源B(new Child()产生的对象),此时资源AB的引用计数都是1,因为只有1个智能指针管理它们,执行到了语句3的时候,是一个智能指针的赋值操作,资源B的引用计数变为了2,同理,执行完语句4,资源A的引用计数也变成了2
  2. 出了函数作用域时,由于析构和构造的顺序是相反的,会先析构共享智能指针c,资源B的引用计数就变成了1;接下来继续析构共享智能指针p,资源A的引用计数也变成了1。由于资源AB的引用计数都不为1,说明还有共享智能指针在使用着它们,所以不会调用资源的析构函数!
  3. 这种情况就是个死循环,如果资源A的引用计数想变成0,则必须资源B先析构掉(从而析构掉内部管理资源A的共享智能指针),资源B的引用计数想变为0,又得依赖资源A的析构,这样就陷入了一个死循环。

要想解决这个问题,只能引入新的智能指针weak_ptr,顾名思义,弱引用,也就是不增加引用计数,它不管理shared_ptr内部管理的指针,他只是起一个监视的作用。它监视的不是shared_ptr本身,而是shared_ptr管理的资源!!!weak_ptr没有重载操作符*->,它不能直接操作资源,但是它可以获取所监视的shared_ptr(如果资源还没有被析构的话)。

weak_ptr的基本用法

weak_ptr使用示例:

#include <iostream>
#include <memory>class Object {
public:Object(int id) : m_id(id) {std::cout << "init obj " << std::endl;}~Object() {std::cout << "bye bye" << m_id << std::endl;}int id() const {return m_id;}
private:int m_id;
};// 取个别名 让写起来更方便
typedef std::shared_ptr<Object> ObjectPtr;void sharedPtrWithWeakPtr() {ObjectPtr obj(new Object(1));typedef std::weak_ptr<Object> WeakObjectPtr;WeakObjectPtr weakObj(obj); //使用共享指针 初始化 弱引用指针//weakObj 仅仅是一个监听者,不会增加引用计数std::cout << "obj use count is " << obj.use_count() << std::endl; // 1{// lock() 方法返回一个 它对应的共享指针// 下面这句话的结果是 2, 而不是1,// 说明weakObj.lock() 内部也得到了一个新的共享指针,所以引用计数+1// 在执行完这句话后就析构掉了,引用计数-1std::cout << "weakObj.lock().use_count() is " << weakObj.lock().use_count() << std::endl; // 2// 由于发生了一次 赋值 ,所以 引用次次数 +1// auto === ObjectPtrauto p = weakObj.lock(); //如果weakObj监视的资源存在, p就存在std::cout << "obj use count is " << obj.use_count() << std::endl; // 2if ( p ) {// do what you want to do} else {}}// 共享指针不再管理任何资源的时候,weakObj的行为// 注意:如果在obj.reset前,还存在共享指针管理它的资源// 如 :ObjectPtr obj1(obj); weakObj.lock();还是有效的obj.reset();{auto p = weakObj.lock();if( p ) {//不应该到这里来std::cout << "weakObj is not null 1" << std::endl;} else {std::cout << "weakObj is null 1" << std::endl;}}// 共享指针管理其他资源的时候,weakObj的行为// 注意:weak_ptr.lock()// 只有在 存在某一个shared_ptr管理的资源和该weak_ptr一样 的时候才有效果!obj.reset(new Object(2));{auto p = weakObj.lock();if( p ) {//不应该到这里来std::cout << "weakObj is not null 2" << std::endl;} else {std::cout << "weakObj is null 2" << std::endl;}}weakObj = obj; // 重新监视  obj// 用weakObj 判断管理的资源是否过期if(weakObj.expired()) {} else {}
}int main() {sharedPtrWithWeakPtr();std::cout << "finished" << std::endl;
}
/*
init obj
obj use count is 1
weakObj.lock().use_count() is 2
obj use count is 2
bye bye1
weakObj is null 1
init obj
weakObj is null 2
bye bye2
finished
*/

由上面的例子可以看出,weak_ptr和初始化它的share_ptr没有关系,而是和share_ptr管理的资源有关系。假如WeakObjectPtr weakObj(obj);,如果obj.reset()weakObj.lock()的返回值就是空,如果obj.reset(new Object(2));,替换了管理对象,则一起的资源就被析构了,weakObj.lock()的返回值同样为空,同样可以推断,如果除了obj以外还有其他共享智能指针一起管理资源,也就是说obj.reset()的时候资源不会被析构,weakObj.lock();的返回值就不会为空了。不明白的话自己写个简单的测试用例就知道了,如:

void sharedPtrWithWeakPtr() {ObjectPtr obj(new Object(1));typedef std::weak_ptr<Object> WeakObjectPtr;WeakObjectPtr weakObj(obj); //使用共享指针 初始化 弱引用指针ObjectPtr obj1 = obj; //注释掉这句话打印的就是error, 加上这句话打印的就是okobj.reset();auto p = weakObj.lock();if( p ) {std::cout << "ok" << std::endl;} else {std::cout << "error" << std::endl;}
}

weak_ptr解决循环引用

weak_ptr可以解决上面的循环引用问题,将Child内部的parent指针换成weak_ptr管理:

#include <iostream>
#include <memory>class Parent;  //Parent类的前置声明typedef std::shared_ptr<Parent> ParentPtr;
typedef std::weak_ptr<Parent> WeakParentPtr;
class Child {
public:WeakParentPtr father;~Child() {std::cout << "bye child" << std::endl;}
};typedef std::shared_ptr<Child> ChildPtr;
//typedef std::weak_ptr<Child> WeakChildPtr;
class Parent {
public://WeakChildPtr son;ChildPtr son;~Parent() {std::cout << "bye parent" << std::endl;}
};void testParentAndChild() {ParentPtr p(new Parent());  // 1  资源AChildPtr c(new Child());  // 2   资源Bp->son = c;     // 3      c.use_count() == 2 and p.use_count() == 1c->father = p;    //  4   c.use_count() == 2 and p.use_count() == 1
}int main() {testParentAndChild();std::cout << "finished" << std::endl;
}
/*
bye parent //成功调用析构函数
bye child
finished
*/

修改为弱引用后,成功的释放了资源,只要将任意一个shared_ptr换成weak_ptr,就可以解决问题。当然,也可以两个都换成weak_ptr,至于这三种方案谁更好,就暂时不清楚了。

shared_from_this()实现原理

std::enable_shared_from_this<T>模板类中有一个weak_ptr,这个weak_ptr用来观测this智能指针,调用shared_from_this()函数的时候,会在内部调用weak_ptrlock()方法,将所观测的shared_ptr返回。这个设计要依赖于当前对象已经有了一个相应的控制块。为此,必须已经存在一个指向当前对象的shared_ptr(比如在调用过shared_from_this()成员函数之外已经有了一个)。假如没有这样shared_ptr存在,那么shared_from_this()会抛异常。 那么这个weak_ptr在什么时候赋值的呢?答案就是在外部第一次构造shared_ptr的时候(如之前的std::shared_ptr<Y> p1(new Y());),对std::enable_shared_from_this<T>进行了赋值(具体实现有点复杂,还不太懂。。),这也就为什么在调用shared_from_this()时,必须存在一个指向当前对象的shared_ptr的原因了。由于这个原因,不要在构造函数中调用shared_from_this(),如:

#include <iostream>
#include <memory>class Y : public std::enable_shared_from_this<Y>
{
public:Y() {std::shared_ptr<Y> p = shared_from_this();}std::shared_ptr<Y> f(){return shared_from_this();}
};int main()
{std::shared_ptr<Y> p1(new Y());}
// 会抛出异常!
/*
terminate called after throwing an instance of 'std::bad_weak_ptr'what():  bad_weak_ptr
*/

unique_ptr独占的智能指针

unique_ptr相对于其他两个智能指针更加简单,它和shared_ptr使用差不多,但是功能更为单一,它是一个独占型的智能指针,不允许其他的智能指针共享其内部的指针,更像原生的指针(但更为安全,能够自己释放内存)。不允许赋值和拷贝操作,只能够移动

#include <iostream>
#include <memory>
#include <cassert>
class Object {
public:Object(int id) : m_id(id) {std::cout << "init obj " << std::endl;}~Object() {std::cout << "bye bye " << m_id << std::endl;}int id() const {return m_id;}
private:int m_id;
};// 让写起来更方便
typedef std::shared_ptr<Object> ObjectPtr;
typedef std::unique_ptr<Object> UniqueObjectPtr;// 只能传递引用 不能传值
void print(const UniqueObjectPtr& obj) {std::cout << obj->id() << std::endl;
}int main() {UniqueObjectPtr obj(new Object(1));
//    UniqueObjectPtr obj1 = obj; // 编译错误,不允许赋值// 获取原生指针auto p = obj.get();if( p ) {} else {}// better 重载了 operator boolif(obj) {} else {}// 重载了 operator -> 和 operator *std::cout << obj->id() << "  " << (*obj).id() << std::endl;print(obj);// 释放管理的指针,由其他东西处理p = obj.release();
//    delete p; 自己负责处理obj.reset(p); //析构之前负责管理的对象,重新管理 p指针obj.reset(); // 析构之前负责管理的对象, 不再管理任何资源// 允许 移动操作UniqueObjectPtr obj1(new Object(1));// obj1已经部管理任何资源 obj2开始管理obj1之前的资源UniqueObjectPtr obj2 = std::move(obj1);assert(obj1 == nullptr);std::cout << obj2->id() << std::endl;// 将unique_ptr管理的内容给 shared_ptrObjectPtr obj3(std::move(obj2));assert(obj2 == nullptr);}/*
init obj
1  1
1
bye bye 1
init obj
1
bye bye 1
*/

unique_ptr管理数组资源不需要指定删除器:

std::shared_ptr<int> p(new int[10], [](int* p){std::cout << "delete[] p" << std::endl;delete[] p; //需要使用delete[]});
std::unique_ptr<int> p2(new int[10]); //不需要指定删除器

性能与安全的权衡

使用智能指针虽然能够解决内存泄漏问题,但是也付出了一定的代价。以shared_ptr举例:

  1. shared_ptr的大小是原始指针的两倍,因为它的内部有一个原始指针指向资源,同时有个指针指向引用计数。
  2. 引用计数的内存必须动态分配。虽然一点可以使用make_shared()来避免,但也存在一些情况下不能够使用make_shared()
  3. 增加和减小引用计数必须是原子操作,因为可能会有读写操作在不同的线程中同时发生。比如在一个线程里有一个指向一块资源的shared_ptr可能调用了析构(因此所指向的资源的引用计数减一),同时,在另一线程里,指向相同对象的一个shared_ptr可能执行了拷贝操作(因此,引用计数加一)。原子操作一般会比非原子操作慢。但是为了线程安全,又不得不这么做,这就给单线程使用环境带来了不必要的困扰。

我觉得还是分场合吧,看应用场景来进行权衡,我也没啥经验,但我感觉安全更重要,现在硬件已经足够快了,其他例如java这种支持垃圾回收的语言不还是用的很好吗。

总结

  • 智能指针主要是使用构造和析构来管理资源的。
  • shared_ptr很好用也很难用,有两种构造方式,使用引用计数实现多人同时管理一份资源。使用this的时候要格外注意。
  • weak_ptr可以解决shared_ptr的循环引用问题。
  • unique_ptr最像裸指针,但更为安全,保证资源的释放,不能复制只能移动。
  • 智能指针带来了性能问题,在不同场合可以选择不同的解决方案。优先使用类的实例(如果内存足够),其次unique_ptr,最后才是shared_ptr

参考

  • 大部分内容出自c++游戏服务器编程
  • 深入应用C++11:代码优化与工程级应用
  • Effective Modern C++

https://www.jiucaihua.cn/news/show-2150185.html

相关文章

用图来教你怎样用Photoshop蓝底转换红底

教你怎样用红底转换成蓝底。PS其实学了这个&#xff0c;你就可以在白底红底蓝底之间不同转变了。 第一步 第二步 第三步 第四步 最后给你们看看对比效果图 转载于:https://www.cnblogs.com/feifei-cyj/p/7795176.html

CentOS 6.5环境下使用HAProxy+apache实现web服务的动静分离

HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理&#xff0c;支持虚拟主机&#xff0c;它是免费、快速并且可靠的一种解决方案。HAProxy特别适用于那些负载特大的web站点&#xff0c;这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬件上&#xff0c;完…

OS之进程管理---多线程模型和线程库(POSIX PTread)

多线程简介 线程是CPU使用的基本单元&#xff0c;包括线程ID&#xff0c;程序计数器、寄存器组、各自的堆栈等&#xff0c;在相同线程组中&#xff0c;所有线程共享进程代码段&#xff0c;数据段和其他系统资源。 传统的的单线程模式是每一个进程只能单个控制线程&#xff0c;但…

可插拔式后台管理系统(Django)

1.实现效果 研究了下django admin的功能&#xff0c;自己实现了一个简单的可插拔式后台管理系统&#xff0c;方便自定义特殊的功能&#xff0c;而且作为一个独立单独的django app&#xff0c;可以整体拷贝到其他项目中作为后台数据管理系统&#xff0c;对数据进行增删改查和自定…

从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之三 || Swagger的使用 3.1

WHY 上文中已经说到&#xff0c;单纯的项目接口在前后端开发人员使用是特别不舒服的&#xff0c;那所有要推荐一个&#xff0c;既方便又美观的接口文档说明框架&#xff0c;当当当&#xff0c;就是Swagger&#xff0c;随着互联网技术的发展&#xff0c;现在的网站架构基本都由原…

[Linux] Linux系统(用户管理)

Linux中有三种用户 Root用户&#xff1a;超级管理员 系统用户&#xff1a;Linux运行某些程序所必需的用户&#xff0c;不建议修改 普通用户&#xff1a;一般修改这个 使用命令groupadd&#xff0c;添加用户组&#xff0c;参数&#xff1a;组名称 在文件/etc/group 里&#xff0…

MATLAB:图像选取局部区域滤波(roicolor、roipoly、roifill、fspecial、roifilt2函数)...

对于某些特殊的图像处理&#xff0c;我们不希望将整张图都进行图像处理。这个时候就用到了roicolor、roipoly、roifill、fspecial、roifilt2函数。代码实现过程如下&#xff1a; close all; %关闭当前所有图形窗口&#xff0c;清空工作空间变量&#xff0c;清…

浅析MySQL使用 GROUP BY 分组聚合与细分聚合

1. 聚合函数(Aggregate Function) MySQL(5.7 ) 官方文档中给出的聚合函数列表&#xff08;图片&#xff09;如下&#xff1a; 详情点击https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html 。 除非另有说明&#xff0c;否则聚合函数都会忽略空值(NULL values)。 …

倪畅的汇编程序——为什么文本文件多了3个字节

学生倪畅编了一个汇编程序&#xff0c;用记事本编辑的&#xff0c;如下图&#xff1a;   他的文件可以下载&#xff0c;点这里…。   程序很简单&#xff0c;目测没问题。   但编译后是这样的&#xff1a;     奇了大怪了&#xff0c;第一行有多余字符&#xf…

Nginx开启SSL支持HTTPS访问(自签名方法)

2019独角兽企业重金招聘Python工程师标准>>> Nginx开启SSL支持HTTPS访问(自签名方法) 超文本传输安全协议&#xff08;缩写&#xff1a;HTTPS&#xff0c;英语&#xff1a;Hypertext Transfer Protocol Secure&#xff09;是超文本传输协议和SSL/TLS的组合&#xff…