• 智能指针面试题


    智能指针被问到的概率还是很大的,特别是Shared_ptr,最好会手撕,亲身经历!

    基本概念

    1. RAll

    RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
    在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

    1. 不需要显式地释放资源。
    2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。

    2.智能指针概念

    在c++中,动态内存的管理式通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是极其困难的。有时使用完对象后,忘记释放内存,造成内存泄漏的问题。
    所谓的智能指针本质就是一个类模板,它可以创建任意的类型的指针对象,当智能指针对象使用完后**,对象就会自动调用析构函数去释放该指针所指向的空间**。
    下面是智能指针的基本框架,所有的智能指针类模板中都需要包含一个指针对象构造函数析构函数

    template<class T>
    class smartptr {
    public:
    	//构造函数
    	smartptr(T*_ptr):ptr(_ptr){}
    
    	//析构函数
    	~smartptr() {
    		if (!ptr) {
    			cout << "smartptr:delete" << endl;
    			delete ptr;
    			ptr = nullptr;
    		}
    	}
    private:
    	T* ptr;//指针对象
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    用户申请堆上的空间,会用一个指针指向将其保存起来,以便于对其进行释放,但往往释放的情况有多种,所以有时会忘记释放,有可能会造成内存泄漏。
    智能指针的提出是为了解决此类问题,将其被封装在一个类中,在构造的时候创建,在析构的时候释放。因为智能指针都是栈空间上类的对象。所以,当函数(程序)运行结束后,会自动调用其析构函数自动释放

    智能指针的定义和使用

    智能指针的使用跟普通指针类似,可以使用运算符“ * " 和 ”-> “去获得指向的对象,因此,我们就需要在类中重载” * " 和" -> "函数

    //类模板
    template<class T>
    class smartptr {
    public:
    	//构造函数
    	smartptr(T*_ptr):ptr(_ptr){}
    
    	//析构函数
    	~smartptr() {
    		if (!ptr) {
    			cout << "smartptr:delete" << endl;
    			delete ptr;
    			ptr = nullptr;
    		}
    	}
    
    	//重载*运算符
    	T& operator*() {
    		return *ptr;
    	}
    
    	//重载->运算符
    	T* operator->() {
    		return ptr;
    	}
    private:
    	T* ptr;//指针对象
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    测试1

    int main() {
    	smartptr<int>ptr1(new int(1));
    	smartptr<string>ptr2(new string("string"));
    	cout << *ptr1 << endl;
    	cout << ptr2->c_str() << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    image.png
    当程序结束时,此时ptr1和ptr2指针被销毁时,对象ptr1和ptr2会自动调用析构函数去释放所指向的资源,这是智能指针特点。

    测试2

    由于我的类中没有定义拷贝构造函数和赋值重载函数,那么我们只能调用类中原生的拷贝构造函数和赋值重载函数。程序就会出现崩溃的问题

    int main() {
    	smartptr<int>ptr1(new int);
    	smartptr<string>ptr2(ptr1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ptr2和ptr1指向的同一块空间,当ptr2被销毁时,它会调用它的析构函数去delete该资源对象,当ptr1被销毁时,也会去调用它的析构函数去释放ptr1所指向的资源.所以,当程序结束时,ptr2被先被销毁,同时释放ptr2所指向的资源,然后ptr1被销毁,也去释放该资源对象,那么如下的资源对象同时被释放两次,所以程序就会被崩溃掉。(资源对象被释放后,如果再去释放该资源,程序就会崩溃)

    总结:

    不能使用原生的拷贝构造函数和赋值重载函数,并且定义的拷贝构造函数和赋值重载函数需要考虑只能释放一次资源对象

    c++库中的智能指针

    1)auto_ptr

    1)如果智能指针指向一个对象A
    2)然后又指向A对象的拷贝B
    3)那么指向A的智能指针会置为nullptr,那么以后再用A对象,就会程序奔溃

    int main() {
    	auto_ptr<int>ptr1(new int);
    	auto_ptr<int>ptr2(ptr1);//ptr1置为nullptr
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    auto_ptr是c++98版本库中提供的智能指针,该指针解决上诉的问题采取的措施是管理权转移的思想,也就是原对象拷贝给新对象的时候,原对象就会被设置为nullptr,此时就只有新对象指向一块资源空间。
    image.png
    如果auto_ptr调用拷贝构造函数或者赋值重载函数后**,如果再去使用原来的对象的话,那么整个程序就会崩溃掉(因为原来的对象被设置为nullptr)**,这对程序是有很大的伤害的.所以很多公司会禁用auto_ptr智能指针。
    image.png

    总结

    优点:管理权转移,防止程序奔溃,原对象拷贝给新对象时,源对象置空
    缺点:源对象置空后,如果再使用源对象,就会程序奔溃,管理权一直在变,且只有一个指针指向

    2)unique_ptr

    unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉,因此,不让其进行拷贝和赋值。 让指针不能被拷贝,即两个unique_ptr不能同时指向同一个对象
    独占式,它确保在任何时候只有一个unique_ptr实例可以拥有对特定资源的所有权。当unique_ptr超出范围或被删除时,它会自动释放所拥有的资源 。
    image.png

    总结

    优点:所有权唯一,禁止拷贝和权限转移,禁用了拷贝构造和赋值重载函数,一个资源只能被一个unique_ptr实例所拥有,防止拷贝后程序奔溃
    缺点:unique_ptr是简单粗暴的防止拷贝,这种比较简单,效率高,但是功能不全面,不支持拷贝和赋值操作。无法共享数据,可自主实现数据共享,但在释放时会导致堆内存重复释放导致系统崩溃。

    3)shared_ptr

    共享式,share_ptr是c++11版本库中的智能指针,shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次,因此是程序不会崩溃掉。

    int main() {
        shared_ptr<int>ptr6(new int(2));
    	cout << ptr6.get() << endl;
    	shared_ptr<int>ptr7 = ptr6;
    	cout<< ptr7.get() << endl;
    	cout << ptr6.get() << endl;
        
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    image.png

    shared_ptr的原理

    image.png
    image.png
    shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源

    1. shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
    2. 当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
    3. 如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
    4. 如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。

    引用计数是用来记录资源对象中有多少个指针指向该资源对象。

    shared_ptrd的实现

    赋值重载的三种情况:

    1. ptr1=ptr1;智能指针自己给自己赋值,不做处理
    2. ptr2=ptr1;如果ptr1和ptr2指向同一块空间,不做处理
    3. ptr2=ptr1;如果ptr2和ptr1指向的空间不一样,处理过程如下:

    image.png

    1. 因为_ptrcount指向的对象是在堆上,因此所有的线程都能够访问到该资源,多线程在修改_ptrcount时,则会出现线程安全问题,因此需要在修改_prtcount时需要用锁来保证其数据的正确性。
    2. “ * "会返回ptr指向的对象,为什么不需要锁对其进行保护?因为ptr返回的对象有可能被读或者被写,这个不是指针内部所考虑的,而是由调用者进行考虑的。
    #include
    #include
    using namespace std;
    
    template<class T>
    class shared_ptr1 {
    public:
    	//构造函数
    	shared_ptr1(T* _ptr):ptr(_ptr) {
    		ptrcount = new int(1);
    		mt = new mutex;
    	}
    
    	void AddCount() {
    		mt->lock();
    		(*ptrcount)++;
    		mt->unlock();
    	}
    
    	shared_ptr1(shared_ptr1<T>& sp) :ptr(sp.ptr), ptrcount(sp.ptrcount), mt(sp.mt) {
    		AddCount();
    	}
    
    	shared_ptr1<T>& operator=(const shared_ptr1<T>& sp) {
    		if (sp.ptr != ptr) {
    			Realse();//释放旧资源
    			ptr = sp.ptr();
    			ptrcount = sp.ptrcount;
    			mt = sp.mt;
    			AddCount();
    		}
    		return *this;
    	}
    
    	//析构函数
    	~shared_ptr1() {
    		Realse();
    	}
    
    	int& use_count() {
    		return *ptrcount;
    	}
    
    	void Realse() {
    		bool deleteflag = false;
    		mt->lock();
    		if (--(*ptrcount) == 0) {
    			delete ptrcount;
    			delete ptr;
    			ptrcount = nullptr;
    			ptr = nullptr;
    			deleteflag = true;
    		}
    
    		mt->unlock();
    		if (deleteflag == true) {
    			delete mt;
    			mt = nullptr;
    		}
    	}
    
    	//重载*运算符
    	T& operator*() {
    		return *ptr;
    	}
    
    	//重载->运算符
    	T* operator->() {
    		return ptr;
    	}
    private:
    	T* ptr;//指针对象
    	int* ptrcount;//引用计数
    	mutex* mt;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    shared_ptr的循环引用

    shared_ptr固然好用,但是它也会有问题存在。假设我们要使用定义一个双向链表,如果我们想要让创建出来的链表的节点都定义成shared_ptr智能指针,那么也需要将节点内的_pre和_next都定义成shared_ptr的智能指针。如果定义成普通指针,那么就不能赋值给shared_ptr的智能指针。
    image.png 当其中两个节点互相引用的时候,就会出现循环引用的现象。如下:

    #include
    using namespace std;
    
    struct ListNode {
    	shared_ptr<ListNode>_pre;
    	shared_ptr<ListNode>_next;
    };
    
    int main() {
    	shared_ptr<ListNode>node1(new ListNode);
    	shared_ptr<ListNode>node2(new ListNode);
    	node1->_next = node2;
    	node2->_pre = node1;
    	cout<<"node1的引用计数:" << node1.use_count() << endl;
    	cout << "node2的引用计数:" << node2.use_count() << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image.png
    ** use_count()**: 返回智能指针对象的引用计数。
    image.png

    1. 当创建出node1和node2智能指针对象时,引用计数都是1.
    2. 当node1的next指向node2所指向的资源时,node2的引用计数就+1,变成2,node2的pre指向noede1所指向的资源时,node1的引用计数+1,变成2.
    3. 当这两个智能指针使用完后,调用析构函数,引用计数都-1,都变成1,由于引用计数不为0,所以node1和node2所指向的对象不会被释放。
    4. 当node1所指向的资源释放需要当node2中的_prev被销毁,就需要node2资源的释放,node2所指向的资源释放就需要当node1中的_next被销毁,就需要node1资源的释放。因此node1和node2都有对方的“把柄”,这两个就造成循环引用现象,最终这node1和node2资源就不会进行释放

    image.png

    总结

    优点:shared_ptr的实现原理是通过引用计数来实现,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可,shared_ptr共享所有权。
    shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次
    缺点:相互引用 ,内存泄漏

    4)week_ptr

    那么如何解决这个shared_ptr的循环引用呢?

    • c++库中存在weak_ptr类型的智能指针。weak_ptr类的对象它可以指向shared_ptr,并且不会改变shared_ptr的引用计数。一旦最后一个shared_ptr被销毁时,对象就会被释放。
    #include
    using namespace std;
    
    struct ListNode {
    	weak_ptr<ListNode>_pre;
    	weak_ptr<ListNode>_next;
    };
    
    int main() {
    	shared_ptr<ListNode>node1(new ListNode);
    	shared_ptr<ListNode>node2(new ListNode);
    	node1->_next = node2;
    	node2->_pre = node1;
    	cout << "node1的引用计数:" << node1.use_count() << endl;
    	cout << "node2的引用计数:" << node2.use_count() << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image.png
    weak_ptr对象指向shared_ptr对象时,不会增加shared_ptr中的引用计数,因此当node1销毁掉时,则node1指向的空间就会被销毁掉,node2类似,所以weak_ptr指针可以很好解决循环引用的问题。
    所以在定义双向链表或者在二叉树等有多个指针的时候,如果想要将该类型定义成智能指针,那么结构体内的指针需要定义成weak_ptr类型的指针,防止循环引用的出现。

    weak_ptr简单实现

    template<class T>
    class weak_ptr1 {
    public:
    	//weak_ptr的构造函数
    	weak_ptr(const shared_ptr<T>&sp):ptr(sp.get()) {}
    
    	//weak_ptr的拷贝构造
    	weak_ptr<T>& operator=(const shared_ptr<T>& sp) {
    		ptr = sp.get();
    		return *this;
    	}
    
    	//重载*运算符
    	T& operator*() {
    		return *ptr;
    	}
    	//重载->运算符
    	T* operator->() {
    		return ptr;
    	}
    private:
    	T* ptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    常见面试题

    智能指针引用计数器无法减一怎么办

    1. 循环引用:循环引用是指两个或多个对象之间相互持有对方的智能指针,导致引用计数器无法归零。这种情况下,智能指针的引用计数永远无法减到零,从而导致资源无法释放。解决循环引用的一种常见方法是使用weak_ptr来打破循环引用关系,弱引用不会增加对象的引用计数,避免了循环引用导致的计数无法减一的问题。
    2. 跨线程使用:如果在多线程环境下,智能指针被跨线程使用,可能会导致引用计数操作不正确,从而无法减一。在多线程环境中,对于引用计数器的操作需要进行同步,以避免竞争条件和数据访问冲突。可以使用互斥锁mutex或其他线程同步机制来保护引用计数的操作,确保线程安全。
    3. 非法使用:智能指针的正确使用方式是通过shared_ptr或unique_ptr等进行引用计数管理。如果不正确地使用普通指针或其他手动管理内存的方式来操作智能指针,可能会导致引用计数无法减一。确保在使用智能指针时遵循正确的使用方式,并避免手动管理智能指针所管理的对象。

    unique_ptr 和shared_ptr的区别

    1.** 所有权管理:**

    • unique_ptr:是独占所有权的智能指针,它拥有对动态分配对象的唯一所有权。当 unique_ptr被销毁或重置时,它所管理的对象也会被销毁。不能将同一个对象的所有权赋予多个unique_ptr
    • shared_ptr:是共享所有权的智能指针,它可以与其他 shared_ptr共享对同一个动态分配对象的所有权。通过引用计数的方式来管理资源,当最后一个 shared_ptr被销毁或重置时,对象才会被销毁。
    1. 内存管理开销:
      • unique_ptr:通常占用更少的内存,因为它只需要额外存储一个指针。不需要维护引用计数。
      • shared_ptr:需要维护引用计数,会有额外的开销,每个 shared_ptr都需要存储一个指针和一个计数器。
        3.** 所需语法:**
      • unique_ptr:使用移动语义,可以通过移动构造函数和移动赋值运算符来传递所有权,不能直接进行拷贝。
      • shared_ptr:可以进行拷贝和赋值操作,使用引用计数来管理对象的生命周期。
    2. 使用场景:
      • unique_ptr:适用于需要独占所有权的情况,例如在容器中存储对象或作为对象成员使用。它是一种轻量级的智能指针,没有引入额外的开销。
      • shared_ptr:适用于需要共享所有权的情况,例如多个对象共享同一个资源、循环引用的情况等。它提供了一种方便的方式来管理动态分配的资源。
        注意:shared_ptr的引用计数机制可能导致循环引用的问题,即两个或多个对象相互持有对方的 shared_ptr,导致资源无法释放。为了避免这种情况,可以使用 weak_ptr来打破循环引用。
        unique_ptr和 shared_ptr在所有权管理和内存管理开销上有所区别,根据不同的需求选择适合的智能指针类型。

    shared_ptr的底层

    1. 指针管理:shared_ptr 内部包含一个指针,用于指向动态分配的对象。这个指针通常是通过 new 运算符分配内存得到的。指针管理部分负责在适当的时候释放对象的内存。
    2. 引用计数:shared_ptr 通过引用计数来跟踪有多少个 shared_ptr 实例共享同一个对象。引用计数通常以整数形式存在,并存储在与所指对象相关联的控制块(control block)中。
    3. 控制块(Control Block):控制块是 shared_ptr 内部的一个数据结构,用于存储引用计数和其他相关信息。控制块通常是动态分配的,并与所指对象共享。
    4. 内存管理:当创建一个 shared_ptr 时,它会与一个控制块关联,并将指针指向所管理的对象。控制块中的引用计数会被初始化为1。当创建其他 shared_ptr 实例并与同一个对象关联时,引用计数会递增。当 shared_ptr 被销毁或重置时,引用计数会递减。当引用计数为0时,表示没有 shared_ptr 实例与该对象关联,此时控制块会负责释放对象的内存。
    5. 线程安全:shared_ptr 的默认实现是线程安全的,它使用原子操作来确保引用计数的并发访问安全。这样可以在多线程环境下安全地共享对象。

    shared_ptr什么时候会改变它的引用计数?

    1. 初始化和拷贝:当创建 shared_ptr 对象时,引用计数会初始化为1。当将一个 shared_ptr 对象赋值给另一个 shared_ptr 对象或进行拷贝构造时,引用计数会增加
    std::shared_ptr<int> ptr1(new int(5));  // 引用计数为1
    std::shared_ptr<int> ptr2 = ptr1;       // 引用计数加1
    
    • 1
    • 2
    1. 重置和赋值:通过 reset 函数或赋值操作符 =shared_ptr 重新指向新的对象,会导致引用计数的变化。旧的对象引用计数减少,新的对象引用计数增加
    std::shared_ptr<int> ptr(new int(5));    // 引用计数为1
    ptr.reset(new int(10));                  // 引用计数减少到0,释放旧对象;引用计数变为1,指向新对象
    
    • 1
    • 2
    1. 解除引用:当 shared_ptr 超出作用域或通过 reset 函数将其置空时,引用计数减少。当引用计数变为0时,内部资源会被释放,即对象会被删除。
    std::shared_ptr<int> ptr(new int(5));  // 引用计数为1
    // 引用计数减少到0,内部对象被删除
    
    • 1
    • 2
    1. 手动修改引用计数:虽然不推荐,但可以通过 shared_ptr的别名获取底层引用计数指针,并手动修改引用计数。但要谨慎操作,确保引用计数的正确性。
      shared_ptr的引用计数会在对象的初始化、拷贝、重置、赋值和解除引用等操作中发生变化。引用计数的准确管理是 shared_ptr能够自动释放对象资源的关键机制。

    拷贝构造函数引用计数会变化吗,赋值会改变吗,哪边变化?

    在拷贝构造函数中,新创建的 shared_ptr 引用计数会增加;
    在赋值操作符中,目标 shared_ptr 的引用计数会增加,而源 shared_ptr(如果有)的引用计数会减少

    unique_ptr指向2个对象,什么时候调用析构函数?

    unique_ptr 是一种独占所有权的智能指针,一个 unique_ptr 对象可以拥有对一个动态分配对象的唯一所有权。当 unique_ptr 被销毁或重置时,它所管理的对象会被析构。
    如果一个 unique_ptr 指向两个不同的对象,例如通过移动语义或者重新分配 unique_ptr 的所有权,那么当 unique_ptr 被销毁或重置时,只会调用其中一个对象的析构函数
    具体来说,当 unique_ptr 被销毁或重置时,它会检查当前是否存在一个有效的指针(即指向一个对象)。如果存在有效的指针,那么会调用该对象的析构函数来销毁它。对于另一个对象,由于它的所有权已经转移到其他 unique_ptr 或已被释放,所以不会调用其析构函数
    需要注意的是,在同一个 unique_ptr 对象上进行多次重置会导致其之前指向的对象被销毁多次,这是一种未定义行为。确保在重置 unique_ptr 之前,将其指向的对象正确释放或移交给其他 unique_ptr 或智能指针。
    总结:unique_ptr 只能拥有对一个对象的唯一所有权,当 unique_ptr 被销毁或重置时,只会调用其中一个对象的析构函数。

    shared_ptr指向2个对象,什么时候调用析构函数?

    shared_ptr 是一种共享所有权的智能指针,它可以与其他 shared_ptr 共享对同一个动态分配对象的所有权。当最后一个 shared_ptr 对象被销毁或重置时,才会调用所管理对象的析构函数。
    如果一个 shared_ptr 指向两个不同的对象,即多个 shared_ptr 共享对这两个对象的所有权,那么当最后一个 shared_ptr 被销毁或重置时,才会调用这两个对象的析构函数
    shared_ptr 内部通过引用计数来跟踪共享的对象,并在引用计数为0时释放对象的内存。每个 shared_ptr 对象都有一个关联的控制块(control block),其中包含引用计数等信息。当一个新的 shared_ptr 对象与现有的对象关联时,引用计数会增加当一个 shared_ptr 对象被销毁或重置时,引用计数会减少。只有当引用计数降为0时,才会调用对象的析构函数
    因此,只有当最后一个 shared_ptr 对象与这两个对象的所有权解除关联时,也就是引用计数降为0时,才会调用这两个对象的析构函数。其他 shared_ptr 对象的销毁或重置不会触发析构函数的调用,只会减少引用计数。
    需要注意的是,使用 shared_ptr 时要注意避免循环引用,即两个或多个对象相互持有对方的 shared_ptr,这会导致对象无法释放,内存泄漏的问题。可以使用 weak_ptr 打破循环引用,以便正确释放对象。

    unique_ptr在函数内使用,指向的是在堆区还是栈区?

    unique_ptr 可以指向在堆区(动态分配)的对象,而不能直接指向栈区(自动分配)的对象。
    在函数内部创建一个 unique_ptr 并使用 new 运算符来分配内存,那么它将指向堆区的对象。这是因为 unique_ptr 是专门用于管理动态分配的对象的智能指针。在函数执行完毕unique_ptr 被销毁时,它会自动释放所管理的堆区对象
    例如,下面的示例演示了在函数内部使用 unique_ptr 来管理动态分配的对象:

    #include 
    
    void foo() {
        std::unique_ptr<int> ptr(new int(10));  // 指向堆区的对象
        // 使用 ptr
        // ...
    }  // 在函数结束时,ptr 被销毁,堆区对象也会被自动释放
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    unique_ptr ptr 在函数内部被创建,并通过 new 运算符在堆区分配了一个整型对象。当函数 foo 结束时,ptr 被销毁,它的析构函数会自动释放堆区对象。
    如果你尝试将 unique_ptr 直接指向栈区对象,会导致编译错误。因为 unique_ptr 的析构函数会尝试删除(delete)指向的对象,而栈区对象在函数结束时会自动被销毁,不能通过 delete 来释放。
    因此,一般情况下,unique_ptr 用于管理在堆区分配的对象,而不是栈区的对象。如果需要管理栈区对象,可以使用原生指针或其他适当的智能指针(如 std::shared_ptr)。

    shared_ptr在函数内使用,指向的是在堆区还是栈区?

    shared_ptr 可以指向在堆区(动态分配)的对象,也可以指向栈区(自动分配)的对象。它并不关心对象是在堆区还是栈区分配的,而是关心对对象的所有权管理。
    当你在函数内部创建一个 shared_ptr 并将其指向堆区对象时,它将管理堆区对象的所有权。当最后一个持有该对象的 shared_ptr 被销毁时,堆区对象将被自动释放。
    当你在函数内部创建一个 shared_ptr 并将其指向栈区对象时,它同样可以管理栈区对象的所有权。但需要注意的是,当函数结束时,栈区对象会被自动销毁,而 shared_ptr 的析构函数会尝试释放对象,这可能导致未定义行为。因此,为了避免这种情况,如果你使用 shared_ptr 管理栈区对象,需要在 shared_ptr 被销毁之前手动释放或者重置它。

    #include 
    void foo() {
        // 在堆区分配对象
        std::shared_ptr<int> ptr1(new int(10));  // 指向堆区的对象
        // 在栈区分配对象
        int value = 20;
        std::shared_ptr<int> ptr2(&value, [](int*) { });  // 指向栈区的对象,自定义删除器为空
        // 使用 ptr1 和 ptr2
        // ...
    }  // 在函数结束时,ptr1 和 ptr2 被销毁,堆区对象会自动释放,栈区对象不会被释放
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上述示例中,ptr1 是指向堆区分配的整型对象的 shared_ptrptr2 是指向栈区的整型对象的 shared_ptr,使用了一个自定义的删除器,但这里删除器为空,因为栈区对象不需要被释放。
    需要注意的是,当你使用 shared_ptr 管理栈区对象时,确保在 shared_ptr 对象被销毁之前,不要访问已经销毁的栈区对象。并且要避免在多个 shared_ptr 之间共享对栈区对象的所有权,以防止悬空指针的问题。一般情况下,shared_ptr 更适合用于管理动态分配的堆区对象。

    当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间?

    智能指针是C++中用于管理动态分配内存的工具,它们提供了自动化的内存管理,可以避免常见的内存泄漏和悬挂指针等问题。以下是几种常见的智能指针及其作用:

    1. unique_ptr:unique_ptr是独占式智能指针,它确保在任何时候只有一个unique_ptr实例可以拥有对特定资源的所有权。当unique_ptr超出范围或被删除时,它会自动释放所拥有的资源。这种智能指针非常适合管理单个动态分配的对象或数组。
    2. shared_ptr:shared_ptr是共享式智能指针,它允许多个shared_ptr实例共享对同一资源的所有权。它使用引用计数技术来跟踪资源的使用情况,并在不再有任何shared_ptr实例引用资源时释放资源。shared_ptr适用于需要共享动态分配的对象或资源的情况,可以防止资源过早释放或悬挂指针的问题。
    3. weak_ptr:weak_ptr也是一种共享式智能指针,但它不会增加引用计数。weak_ptr通常与shared_ptr一起使用,用于解决循环引用的问题。循环引用指两个或多个对象彼此持有shared_ptr,并因此无法释放。通过使用weak_ptr,可以在需要时获取shared_ptr的临时拷贝,而不会增加引用计数,从而打破循环引用。
      这些智能指针的作用是确保动态分配的内存资源在不再使用时能够正确释放,从而提高程序的内存安全性和可靠性。它们减少了手动管理内存的工作量,避免了内存泄漏和悬挂指针等常见错误。使用智能指针可以简化代码,并提高代码的可读性和可维护性。然而,需要注意避免循环引用的问题,以免导致资源泄漏。

    如果智能指针在函数里使用,会在什么时候释放 ?

    智能指针的释放时机取决于其作用域和所有权的转移。

    1. unique_ptr:当unique_ptr超出其作用域时(例如函数结束、代码块结束),或者通过std::move将所有权转移给其他unique_ptr时,unique_ptr将自动释放所管理的资源。这意味着在函数内部使用unique_ptr时,当函数返回或代码块结束时,unique_ptr将自动释放所拥有的资源
    2. shared_ptr:shared_ptr使用引用计数来跟踪资源的使用情况。当最后一个引用到shared_ptr的实例超出作用域时,即没有任何shared_ptr实例引用该资源时,引用计数会变为零,资源将被释放。因此,在函数内部使用shared_ptr时,只有在最后一个引用离开作用域时,资源才会被释放
    3. weak_ptr:weak_ptr本身并不拥有资源,它只是观察shared_ptr的生命周期。当最后一个shared_ptr超出作用域时,资源被释放,无论是否有weak_ptr观察。因此**,weak_ptr不直接参与资源的释放**。
      总结起来,无论是unique_ptr还是shared_ptr,在函数内部使用时,智能指针的释放时机取决于其作用域和所有权的转移。一般情况下,当智能指针超出其作用域时,或者将所有权转移到其他智能指针时,资源将被自动释放。这种自动释放的机制可以有效地避免内存泄漏和资源管理的问题。
  • 相关阅读:
    CAD如何自定义快捷键
    Xilinx FPGA 编程技巧之常用时序约束
    kafka消费者组
    基于SSM的校园订餐管理系统
    多端开发之uniapp开发app
    Linuxd中常见命令
    实践6 WDG
    Windows11 OneDrive 安装后无法打开的解决办法
    【jmeter+ant+jenkins】之搭建 接口自动化测试平台
    高效搜索,提升编程效率
  • 原文地址:https://blog.csdn.net/weixin_63787588/article/details/134458428