八股篇C++面试八股篇|通过手撕来习惯使用智能指针
AKoalas智能指针是C++11标准在RAII思想下引入智能指针库,其中包括 std::shared_ptr
、std::unique_ptr
和 std::weak_ptr
。现代C++编程中经常使用智能指针,而且在当前程序员人才过剩时代,面试手撕成了常态。
智能指针
C++程序设计中使用堆内存是非常频繁的操作,在工作开发业务逻辑模块时,经常会出现使用多个对象进行数据传输。而堆内存的申请和释放都由程序员自身控制,一不留神可能会导致内存泄漏,后期排查起来要了命。
C++里面的四个智能指针: auto_ptr
、unique_ptr
、shared_ptr
、 weak_ptr
其中后三个是C++11支持, 并且第一个已经被C++11弃用。
auto_ptr
C98就诞生的智能指针,不过存在缺陷。
主要问题包括:
auto_ptr
在拷贝构造和赋值操作中会转移所有权,容易导致潜在的内存泄漏问题。
auto_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 29 30 31
| #include <iostream> #include <memory>
class MyClass { public: MyClass() { std::cout << "MyClass Constructor" << std::endl; }
~MyClass() { std::cout << "MyClass Destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; } };
int main() { std::auto_ptr<MyClass> ptr(new MyClass());
ptr->doSomething();
return 0; }
|
现代编译环境更严格,并且运用场景更复杂,该智能指针已被弃用。
std::unique_ptr
std::unique_ptr
正如指针名字一样,独一无二的。它不允许其他的智能指针共享其内部的指针,不允许通过赋值将 一个unique_ptr
赋值给另一个unique_ptr
。
但是通过std::move
可以进行所有权转移,原指针则不再拥有控制权,并被置为空。
1 2 3 4 5 6 7
| unique_ptr<std::string> my_ptr(new std::string); cout << "my_ptr:" << my_ptr << endl;
unique_ptr<std::string> my_other_ptr = std::move(my_ptr);
cout << "my_other_ptr:" << my_other_ptr << endl; cout << "my_ptr:" << my_ptr << endl;
|
std::make_unique初始化
std::make_unique
是 C++14 标准库中的一个函数,用于创建一个带有给定参数的 std::unique_ptr
。它用于创建具有自动内存管理的动态分配对象。
以下是 std::make_unique
的一般语法:
1
| std::make_unique<T>(args...)
|
其中:
T
是要创建的对象的类型。
args...
是要传递给 T
构造函数的参数。
例如,如果您想要创建一个指向值为 42
的整数的 std::unique_ptr
,可以使用 std::make_unique
如下所示:
1
| auto ptr = std::make_unique<int>(42);
|
这将创建一个指向值为 42
的整数的 std::unique_ptr<int>
。
std::share_ptr
std::share_ptr
也如它的名字一样,将指针分享使用。所以在std::share_ptr内部有一个引用计数,每一个拷贝都只向同一个内存。当最后一个计数的指针被析构时,该内存才会被释放。
1、std::share_ptr初始化
1 2 3 4 5 6 7 8 9 10 11 12
| std::shared_ptr<int> share_p1(new int(1)); std::shared_ptr<int> share_p2 = share_p1; std::shared_ptr<int> share_p3; share_p3.reset(new int(1)); if (share_p3) { cout << "p3 is not null"; }
p3 is not null p2 address:0000020E43C86B70 p3 address:0000020E43C86C30
|
2、std::make_shared初始化
std::make_shared
是C++11标准库中的一个函数,它比普通初始化方式更高效。主要是因为:
- 减少内存分配次数:
std::make_shared
会一次性分配内存用于存储对象和控制块(用于引用计数等),而不是分别为对象和控制块分配内存。
- 相比之下,使用
new
和 std::shared_ptr
分别分配对象和控制块会导致两次内存分配,增加了内存分配的开销。
- 减少内存碎片:
- 由于
std::make_shared
一次性分配了对象和控制块所需的内存,可以减少内存碎片的产生。
- 使用
new
和 std::shared_ptr
分别分配对象和控制块可能会导致内存碎片的产生,影响内存的利用效率。
- 更好的缓存局部性:
- 由于
std::make_shared
一次性分配了连续的内存块用于存储对象和控制块,这种连续存储的方式有利于缓存局部性,提高访问效率。
- 相比之下,使用
new
和 std::shared_ptr
分别分配的内存块可能不是连续的,降低了缓存局部性,影响访问效率。
1
| auto share_p4 = std::make_shared<int>(100);
|
3、std::share_ptr的循环引用
循环引用是share_ptr
的特殊情况,该情况会导致内存泄漏,比如下面两个例子,Aptr
和Bptr
互相指向对方,导致互相引用计数都是2。当结束程序运行时,两个指针计数都无法为0,导致无法析构,从而内存泄漏。
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
| class Aptr; class Bptr;
class Aptr { public: std::shared_ptr<Bptr> bptr; ~Aptr() { cout << "A is deleted" << endl; } };
class Bptr { public: std::shared_ptr<Aptr> aptr; ~Bptr() { cout << "B is deleted" << endl; } };
int main() { std::shared_ptr<Aptr> pa; std::shared_ptr<Aptr> ap(new Aptr); std::shared_ptr<Bptr> bp(new Bptr); ap->bptr = bp; bp->aptr = ap; pa = ap; ap->bptr.reset(); cout << "main leave. pa.use_count():" << pa.use_count() << endl; }
|
为了避免循环引用发生,std::weak_ptr
用来协助管理。
std::weak_ptr
std::weak_ptr
是 C++11 中引入的一种智能指针,用于解决 std::shared_ptr
的循环引用(circular reference)问题。std::weak_ptr
是一种弱引用指针,它不会增加引用计数,也不会拥有被指向对象的所有权,因此不会影响对象的生命周期。
- 解决循环引用问题:
- 当两个对象相互持有对方的
std::shared_ptr
时,会导致循环引用,使得对象无法被正确释放,造成内存泄漏。std::weak_ptr
可以打破这种循环引用,避免内存泄漏。
- 安全地访问被
std::shared_ptr
管理的对象:
- 通过
std::weak_ptr
可以安全地访问被 std::shared_ptr
管理的对象,因为 std::weak_ptr
不会增加引用计数,当对象被释放后,std::weak_ptr
会自动变为一个空指针。
- 使用前需要进行
lock
操作:
- 为了访问
std::weak_ptr
指向的对象,需要通过 lock
方法将 std::weak_ptr
转换为 std::shared_ptr
,如果对象存在,则返回一个有效的 std::shared_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
| std::weak_ptr<int> gw; void getWeakptr() { if (gw.expired()) { cout << "gw无效,资源已释放" << endl;; } else { auto spt = gw.lock(); cout << "gw有效, *spt = " << *spt << endl; } }
int main() { { auto sp = std::make_shared<int>(42); gw = sp; getWeakptr(); } getWeakptr(); }
|
针对上面的循环引用只需要把其中一个智能指针改成weak_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 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
| class ClassB;
class ClassA { public: std::shared_ptr<ClassB> bPtr;
void doSomething() { std::cout << "ClassA is doing something" << std::endl; }
~ClassA() { std::cout << "ClassA destructor called" << std::endl; } };
class ClassB { public: std::weak_ptr<ClassA> aWeakPtr;
void doSomething() { std::cout << "ClassB is doing something" << std::endl; if (auto aPtr = aWeakPtr.lock()) { aPtr->doSomething(); } else { std::cout << "ClassA object has been destroyed" << std::endl; } }
~ClassB() { std::cout << "ClassB destructor called" << std::endl; } };
void testWeak2Share() { std::shared_ptr<ClassA> aPtr = std::make_shared<ClassA>(); std::shared_ptr<ClassB> bPtr = std::make_shared<ClassB>();
aPtr->bPtr = bPtr; bPtr->aWeakPtr = aPtr;
bPtr->doSomething(); }
int main() { testWeak2Share(); }
|
在上面的示例中,ClassA
持有ClassB
的shared_ptr
,而ClassB
持有ClassA
的weak_ptr
。通过使用weak_ptr
,我们成功解决了循环引用的问题,避免了内存泄漏。
当ClassB
调用doSomething()
方法时,它会尝试获取ClassA
的shared_ptr
,如果ClassA
对象还存在,则可以正常调用doSomething()
方法;否则会输出提示信息。
实现智能指针
模拟实现std::unique_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 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
| #include <iostream> #include <memory>
using namespace std;
template<class T> class UniquePtr { public: UniquePtr(T* ptr = nullptr) : _ptr(ptr) {}
~UniquePtr() { if (_ptr) delete _ptr; }
UniquePtr(UniquePtr&& other) noexcept : _ptr(other._ptr) { other._ptr = nullptr; }
UniquePtr& operator=(const UniquePtr&& other) noexcept { if (this != &other) { delete _ptr; _ptr = other._ptr; other._ptr = nullptr; } return *this; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
friend std::ostream& operator<<(std::ostream& os, const UniquePtr& myPtr) { if (myPtr._ptr) { os << myPtr._ptr; } else { os << "nullptr"; } return os; }
private:
UniquePtr(UniquePtr<T> const&) = delete; UniquePtr& operator=(UniquePtr<T> const&) = delete;
private: T* _ptr; };
int main() { UniquePtr<std::string> my_ptr(new std::string);
cout << "my_ptr:" << my_ptr << endl; UniquePtr<std::string> my_other_ptr = std::move(my_ptr);
system("pause"); return 0; }
|

模拟实现std::share_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 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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| #include <iostream> #include <memory> #include <thread> #include <mutex>
using namespace std;
template<class T> class SharePtr { public: SharePtr(T* ptr = nullptr) :_ptr(ptr) ,_pRefCount(new int(1)) {
}
~SharePtr() { std::cout << _ptr << endl; Release(); }
SharePtr(const SharePtr<T>& sp) : _ptr(sp._ptr) , _pRefCount(sp._pRefCount) { AddRefCount(); }
SharePtr<T>& operator=(const SharePtr<T>& sp) { if (_ptr != sp._ptr) { Release(); _ptr = sp._ptr; _pRefCount = sp._pRefCount; _pMutex = sp._pMutex;
AddRefCount(); } return *this; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
int UseCount() { return *_pRefCount; } T* Get() { return _ptr; }
void AddRefCount() { _pMutex.lock(); ++(*_pRefCount); _pMutex.unlock();
}
friend std::ostream& operator<<(std::ostream& os, const SharePtr& myPtr) { if (myPtr._ptr) { os << myPtr._ptr; } else { os << "nullptr"; } return os; }
private: void Release() { bool deleteflag = false; _pMutex.lock(); if (--(*_pRefCount) == 0) { delete _ptr; delete _pRefCount; deleteflag = true;
std::cout << "Share was Release" << endl; } _pMutex.unlock(); }
private: T* _ptr; int* _pRefCount; mutex _pMutex; };
void TestMySharePtr() { SharePtr<int> share_p1(new int(1)); SharePtr<int> share_p2 = share_p1; SharePtr<int> share_p3;
std::cout << "share_p1:" << share_p1 << endl; std::cout << "share_p2:" << share_p2 << endl; std::cout << "share_p3:" << share_p3 << endl;
std::cout << "share_p1 use_count:" << share_p1.UseCount() << endl; std::cout << "share_p2 use_count:" << share_p2.UseCount() << endl; std::cout << "share_p3 use_count:" << share_p3.UseCount() << endl; }
int main() { TestMySharePtr(); system("pause"); return 0; }
|
