前言
c++中的指针无疑是c++的精髓,极大地提升了编程的灵活性,但各种奇怪的泄漏问题也接踵而至。
内存泄漏是指程序中已经动态分配的堆内存未被释放或者无法释放,通常是由于通过new/new[]分配的对象没有匹配地调用delete/delete[],造成系统内存的浪费,严重时甚至会导致系统崩溃。
为了解决这些问题,c++引入了auto_ptr,但由于auto_ptr引入了更多的问题,通常被弃用。所以c++11中引入了三种新的智能指针:shared_ptr、weak_ptr和unique_ptr。智能指针基于RAII的思想,将指针封装为对象,在对象初始化时申请内存,对象析构时释放内存。这样就使得智能指针在离开其作用域时,自动调用析构函数释放内存,保证了内存安全。
unique_ptr
unique_ptr是不可共享的智能指针,这种智能指针通过将拷贝构造函数和赋值运算符声明为私有的,避免了unique_ptr在外部被引用,从而保证其唯一性。下面代码提供了一种unique_ptr的实现。
template <typename T>
class my_unique_ptr {
public:
my_unique_ptr() : t_(nullptr){}
my_unique_ptr(T* t) : t_(t){}
~my_unique_ptr() {
delete t_;
}
my_unique_ptr& operator*() {
return *this;
}
T* operator->() {
return t_;
}
T* get() const {
return t_;
}
private:
T* t_;
my_unique_ptr& operator=(const my_unique_ptr& rhs) {
t_ = rhs.t_;
return *this;
}
my_unique_ptr(const SharedPtr& rhs) {
t_ = rhs.t_;
}
};
shared_ptr和weak_ptr
shared_ptr是可共享的智能指针,为解决其释放问题,shared_ptr引入了引用计数,下图为下面代码对应的模型,每次对同一shared_ptr的引用,都会使得其引用计数加一。
#include <bits/stdc++.h>
int main(){
std::shared_ptr<int> p1 = std::make_shared<int>(4);
std::cout << p1.use_count() << std::endl; //引用计数为1
std::shared_ptr<int> p2 = p1;
std::cout << p1.use_count() << std::endl; //引用计数为2
return 0;
}
在释放shared_ptr的时候,先将引用计数减一,此时若引用计数为0,则释放内存,否则不释放内存。
有时我们会遇到如下框架的代码:
#include <bits/stdc++.h>
class B;
class A{
public:
std::shared_ptr<B> b;
};
class B{
public:
std::shared_ptr<A> a;
};
int main(){
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<B> p2 = std::make_shared<B>();
p1->b = p2;
p2->a = p1;
std::cout << p1.use_count() << std::endl; //引用计数为2
std::cout << p2.use_count() << std::endl; //引用计数为2
return 0;
}
在打印结果中,p1和p2的引用计数均为2,在离开作用域时,引用计数均减一变为1,但都没有释放内存,造成了内存泄漏。这种问题我们称之为shared_ptr的循环引用,为了解决循环引用问题,c++11引入了weak_ptr,weak_ptr不参与引用计数,如下面代码。
class B;
class A{
public:
std::weak_ptr<B> b;
};
class B{
public:
std::weak_ptr<A> a;
};
int main(){
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<B> p2 = std::make_shared<B>();
p1->b = p2;
p2->a = p1;
std::cout << p1.use_count() << std::endl; //引用计数为1
std::cout << p2.use_count() << std::endl; //引用计数为1
return 0;
}
这里也提供一种shared_ptr的简单实现。
template <typename T>
class my_shared_ptr {
public:
my_shared_ptr() : t_(nullptr), count_(nullptr) {
}
my_shared_ptr(T* t) : t_(t), count_(nullptr) {
if(t) { count_ = new int(1); }
}
my_shared_ptr(const SharedPtr& rhs) {
t_ = rhs.t_;
count_ = rhs.count_;
if(count_) {
(*count_)++;
}
}
~my_shared_ptr() {
reset();
}
my_shared_ptr& operator=(const my_shared_ptr& rhs) {
if(this == &rhs) {
return *this;
}
reset();
t_ = rhs.t_;
count_ = rhs.count_;
if(count_) {
(*count_)++;
}
return *this;
}
my_shared_ptr& operator*() {
return *this;
}
T* operator->() {
return t_;
}
T* get() const {
return t_;
}
int use_count() const {
return count_ ? *count_ : 0;
}
void reset() {
if(count_) {
(*count_)--;
if(*count_ == 0) {
delete t_;
delete count_;
}
}
}
private:
T* t_;
int* count_;
};
总结
不管是unique_ptr和shared_ptr,个人理解都是在处理指针所有权的问题,unique_ptr避免指针的浅拷贝,shared_ptr通过引用计数共享所有权,如果想移交所有权,可以使用std::move移动语义,这里我们下篇文章继续讨论。