智能指针是为了更方便的管理内存而设计的,设计思想就是让使用者不再管理内存,而是由智能指针来进行管理。
换句话说以后不用再考虑new
出来的对象什么时候需要delete
,智能指针能帮你管理内存。
智能指针分为三种: std::shared_ptr
、std::weak_ptr
和std::unique_ptr
。
std::unique_ptr
std::unique_ptr
指针拥有其管理对象的所有权,该智能指针不能被复制,只能被移动。当std::unique_ptr
智能指针被析构,则其管理的对象也会被析构。
举一个简单的例子,高中宿管大爷,早六点开灯,晚十点关灯,其他人没有办法参与开关灯的事情。这里开关灯指的就是内存的分配与释放。
用法示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <memory> int main() { { int* p = new int(0); std::unique_ptr<int> up = std::unique_ptr<int>(p);
}
{ auto up = std::make_unique<int>(0); } }
|
下面看一下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
| template<typename OBJECT> class unique_ptr { public: unique_ptr(OBJECT* p) : p_(p) {} ~unique_ptr(){ if(p_ != nullptr){ delete p_; } } unique_ptr(const unique_ptr<OBJECT>& other) = delete; unique_ptr<OBJECT>& operator=(const unique_ptr<OBJECT>& other) = delete; unique_ptr(unique_ptr&& other) { p_ = other.p_; other.p_ = nullptr; }
unique_ptr<OBJECT>& unique_ptr(unique_ptr&& other) { if (&other == this) { return *this; } delete p_; p_ = other.p_; other.p_ = nullptr; } private: OBJECT* p_ = nullptr; };
|
见示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class object { public: object() { std::cout << "object()" << std::endl; } ~object() { std::cout << "~object()" << std::endl; } };
int main() { { unique_ptr<object> p(new object); unique_ptr<object> other(std::move(p)); } return 0; }
|
运行结果:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| template <typename OBJECT> class shared_ptr { public: shared_ptr(OBJECT* object):p_(object), count_(new int(0)) { ++(*count_); }
~shared_ptr() { if(--(*count_) == 0) { delete p_; delete count_; } }
shared_ptr(const shared_ptr& other) { count_ = other.count_; ++(*count_); p_ = other.p_; }
shared_ptr<OBJECT>& operator=(const shared_ptr& other) { if (&other == this) { return &this; } count_ = other.count_; ++(*count_); delete p_; p_ = other.p_; return &this; }
private: OBJECT* p_ = nullptr; int* count_ = nullptr; };
|
运行一下例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class object { public: object() { std::cout << "object()" << std::endl; }
~object() { std::cout << "~object()" << std::endl; } };
int main() { { shared_ptr<object> sp(new object);
auto sp1(sp); auto sp2(sp); auto sp3(sp); auto sp4(sp); auto sp5(sp); auto sp6(sp); } return 0; }
|
运行结果:
\resource\C++智能指针简介与错误使用情况
可以看到该对象被构造了一次,又被析构了一次。
上面的实现虽然让内存管理变得简单,但也带来了一些麻烦。
由于智能指针拥有了对象的管理权,万一两个智能指针管理同一对象,那么这两个智能指针在析构时会对同一对象执行两次delete
, 从而造成崩溃。见下面代码举例。
1 2 3 4 5 6 7 8
| int main() { { object* p = new object(); shared_ptr<object> sp1(p); shared_ptr<object> sp2(p); } return 0; }
|
上方代码运行会崩溃。因为两个截然不同的智能指针sp1
和sp2
同时管理了同一个object
对象, 它们分别析构时会对指针p
,delete
两次。
所以我们更加推荐使用std::make_shared
来代替使用裸指针初始化智能指针。见下面举例。
1 2 3
| std::shared_ptr<object> sp = std::make_shared<object>();
|
特殊情况
我们有时会在代码里遇到这样一种情况,见下面代码。
1 2 3 4 5 6
| class error_object { public: std::shared_ptr<error_object> get_sp() { return std::shared_ptr<error_object>(this); } };
|
上面代码中get_sp()
的函数,目的是想返回一个能够管理自己的智能指针,但是我们可以看到每调用一次get_sp(),我们都会用同一个指针this
, 创建一个不同的智能指针。
这意味着我们调用两次get_sp()
函数后,程序运行时会崩溃。
为了应对这种情况标准库中设计了一个工具函数std::enable_shared_from_this
, 具体用法如下。
1 2 3 4 5 6 7 8 9
| class correct_object : public std::enable_shared_from_this<correct_object> { };
int main() { auto instance = std::make_shared<correct_object>(); std::shared_ptr<correct_object> sp1 = instance.shared_from_this(); std::shared_ptr<correct_object> sp2 = instance.shared_from_this(); return 0; }
|
上面代码可以正常运行。
关于std::enable_shared_from_this
的实现原理,见C++ enable_shared_from_this原理与简单实现
。
std::weak_ptr
std::weak_ptr
不能被称为一个独立的智能指针,它是std::shared_ptr
智能指针的一种扩展。
std::weak_ptr
的功能是,观察一个被std::shared_ptr
管理的对象, 但不会影响std::shared_ptr
的引用计数。
std::weak_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 26 27
| class object { public: object() { std::cout << "object()" << std::endl; } ~object() { std::cout << "~object()" << std::endl; } };
int main() { std::weak_ptr<object> weak; { auto sp = std::make_shared<object>(); weak = sp; std::shared_ptr<object> sp1 = weak.lock(); if(sp1){ sp1->doSomeThing(); } } if (weak.expired()) { std::cout << "资源没有释放" << std::endl; } else { std::cout << "资源已经释放" << std::endl; } }
|
以下代码出现了引用回环,会导致智能指针对象被销毁了,但资源没有被销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class list_node { public: list_node() { std::cout << "list_node()" << std::endl; }
~list_node() { std::cout << "~list_node()" << std::endl; } std::shared_ptr<list_node> next; };
int main() { { auto sp0 = std::make_shared<list_node>(); auto sp1 = std::make_shared<list_node>(); auto sp2 = std::make_shared<list_node>(); sp0->next = sp1; sp1->next = sp2; sp2->next = sp0; } return 0; }
|
运行结果:
我们可以看到该对象的析构函数一个也没有执行,说明内存没有释放。
我们现在换成std::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
| class list_node { public: list_node() { std::cout << "list_node()" << std::endl; }
~list_node() { std::cout << "~list_node()" << std::endl; } std::weak_ptr<list_node> next; };
int main() { { auto sp0 = std::make_shared<list_node>(); auto sp1 = std::make_shared<list_node>(); auto sp2 = std::make_shared<list_node>(); sp0->next = sp1; sp1->next = sp2; sp2->next = sp0; } return 0; }
|
运行结果:
现在指针形成的环路被std::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
| #include <iostream> #include <memory> #include <string> #include <vector>
class subscriber { public: void read(const std::string& str) { std::cout << str << std::endl; } };
class boardcaster { public: void regist(std::shared_ptr<subscriber> sp) { vec.push_back(sp); }
void boardcast(const std::string& str) { for (auto it = vec.begin(); it != vec.end();) { auto sp = it->lock(); if (sp) { sp->read(str); it++; } else { std::cout << "subscriber is delete!" << std::endl; it = vec.erase(it); } } }
private: std::vector<std::weak_ptr<subscriber>> vec; };
int main() { boardcaster obj; auto reader0 = std::make_shared<subscriber>(); obj.regist(reader0); { auto reader1 = std::make_shared<subscriber>(); obj.regist(reader1); auto reader2 = std::make_shared<subscriber>(); obj.regist(reader2);
obj.boardcast("start boardcast!"); }
obj.boardcast("reader1 and reader2 is deleted, boardcast angin!"); return 0; }
|
智能指针(现代 C++)
C/C++内存泄漏及检测
知乎 C++ 怎么检测内存泄露,怎么定位内存泄露?
1 2 3 4 5 6 7 8 9 10 11
| #ifdef _WIN32 #include <crtdbg.h> #ifdef _DEBUG
#define new new(_NORMAL_BLOCK,__FILE__,__LINE__) #endif #endif
#ifdef _WIN32 _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)|_CRTDBG_LEAK_CHECK_DF); #endif
|
使用 CRT 库查找内存泄漏