智能指针
● 使用 new 与 delete 的问题:内存所有权不清晰,容易产生不销毁,多销毁的情况
int* fun()
{
int* res = new int(100); //fun()拥有对fun()申请的内存的销毁权
return res;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int* y = fun(); //main()也拥有对fun()申请的内存的销毁权
return a.exec();
}
int* fun()
{
static int res = 100; //fun()拥有对fun()申请的内存的销毁权
return &res;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int* y = fun(); //main()可能无权销毁fun()中的静态数据
return a.exec();
}
● C++ 的解决方案:智能指针
– auto_ptr ( C++17 删除)
– shared_ptr / uniuqe_ptr / weak_ptr
● shared_ptr——基于引用计数的共享内存解决方案
– 基本用法
#include<memory> //包含头文件memory
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
std::shared_ptr<int> x(new int(3)); //声明等价于int* x(new int(3))
std::cout << *x << std::endl;
return a.exec();
}
#include<memory>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
std::shared_ptr<int> x(new int(3)); //通过引用计数(同一时刻该内存的使用者数量)来决定什么时候销毁,不会产生内存泄漏。此时引用计数为1
std::cout << x.use_count() << std::endl; //输出1
std::shared_ptr<int> y = x; //OK,不会产生内存泄漏。与x共享引用计数对象,此时引用计数为2
std::cout << y.use_count() << std::endl; //输出2
std::cout << x.use_count() << std::endl; //输出2
//程序结束先销毁y,调用y的析构函数,引用计数变为1,再销毁x,调用x的析构函数,引用计数变为0,调用delete
return a.exec();
}
#include<memory>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
std::shared_ptr<int> x(new int(3));
std::cout << x.use_count() << std::endl; //输出1
{
std::shared_ptr<int> y = x;
std::cout << y.use_count() << std::endl; //输出2
//因为在语句体当中,语句结束销毁y
}
std::cout << x.use_count() << std::endl; //输出1
return a.exec();
}
#include<memory>
std::shared_ptr<int> fun()
{
std::shared_ptr<int> res(new int(100));
std::cout << res.use_count() << std::endl; //输出1
return res;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
auto y = fun();
std::cout << y.use_count() << std::endl; //输出1
//主函数结束,引用计数变为1,调用delete
return a.exec();
}
– reset / get 方法
std::shared_ptr::reset
std::shared_ptr::get
#include<memory>
std::shared_ptr<int> fun()
{
std::shared_ptr<int> res(new int(100));
return res;
}
void fun2(int*)
{
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
auto y = fun();
std::cout << *y << std::endl; //y的类型std::shared_ptr<int>
std::cout << *(y.get())<< std::endl; //y.get()的类型为int*。get() 返回存储的指针,而非被管理指针。
fun2(y); //类型不匹配
fun2(y.get()); //兼容C风格指针,支持相互调用
return a.exec();
}
#include<memory>
std::shared_ptr<int> fun()
{
std::shared_ptr<int> res(new int(100));
return res;
}
void fun2(int*)
{
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
auto y = fun();
y.reset(new int(3)); //释放原先的内存,关联到新内存
y.reset((int*)nullptr); //等价于std::shared_ptr<int> z;。nullptr不能隐式转换为int*,要加上(int*)
return a.exec();
}
– 指定内存回收逻辑
std::shared_ptr::shared_ptr:template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
#include<memory>
void fun(int* ptr)
{
std::cout << "void fun(int* ptr)" << std::endl;
delete ptr;
}
int main(int argc, char *argv[])
{
std::shared_ptr<int> res(new int(3), fun);
}
void dummy(int*)
{
std::cout << "void dummy(int*)\n";
}
std::shared_ptr<int> fun()
{
static int res = 3;
return std::shared_ptr<int>(&res, dummy);
}
int main(int argc, char *argv[])
{
auto y = fun();
return 0;
}
– std::make_shared
int main(int argc, char *argv[])
{
std::shared_ptr<int> ptr(new int(3)); //ptr保存了申请的动态内存的地址和引用计数(也在堆上)的地址
std::shared_ptr<int> ptr2 = ptr; //OK,希望和ptr共享同一个引用计数
auto ptr3 = std::make_shared<int>(3); //OK,构造了动态内存和引用计数,make_shared引入了优化,使得这两块内存距离足够近,属于同一条缓存线,可以同时被读入缓存,只需一次访存,就可以完成引用计数-1并调用delete,提升了性能
return 0;
}
– 支持数组( C++17 支持 shared_ptr<T[]> ; C++20 支持 make_shared 分配数组)
#include<memory>
int main(int argc, char *argv[])
{
//Before C++17, 指定内存回收逻辑,释放对象数组
std::shared_ptr<int[]> ptr(new int[5]); //Since C++17
auto ptr2 = std::make_shared<int[]>(5); //Since C++20, 等价于std::shared_ptr<int[]> ptr2 = std::make_shared<int[]>(5);
return 0;
}
– 注意: shared_ptr 管理的对象不要调用 delete 销毁
#include<memory>
int main(int argc, char *argv[])
{
std::shared_ptr<int> m(new int(3)); //shared_ptr已经包含delete m.get();
delete m.get(); //Error: double free detected
std::shared_ptr<int> x(new int(3)); //shared_ptr已经包含delete x.get();
std::shared_ptr<int> y(x); //OK,x的内存信息和引用计数信息都传递给y
std::shared_ptr<int> y(x.get()); //Error: double free detected,系统认为y拥有这块内存的拥有权,并将引用计数置为1,销毁y的时候,引用计数-1并释放内存,销毁x的时候又是引用计数-1并释放内存,导致错误
return 0;
}
● unique_ptr——独占内存的解决方案
– 基本用法
– unique_ptr 不支持复制,但可以移动
#include<iostream>
#include<new>
#include<memory>
int main(int argc, char *argv[])
{
std::unique_ptr<int> x(new int(3)); //x独享new int(3)占用的内存
std::cout << x.get() << '\n';
//std::unique_ptr<int> y = x; //Error
std::unique_ptr<int> z = std::move(x); //OK
std::cout << x.get() << '\n'; //x的内容已经移动到z里了
std::cout << z.get() << '\n'; //打印原先x独享的内存
return 0;
}
#include<iostream>
#include<new>
#include<memory>
std::unique_ptr<int> fun()
{
//std::unique_ptr<int> res(new int(3));
auto res = std::make_unique<int>(3); //等价于上面的语句
return res;
}
int main(int argc, char *argv[])
{
std::unique_ptr<int> x = fun();
std::cout << x.get() << std::endl;
return 0;
}
– 为 unique_ptr 指定内存回收逻辑
#include<iostream>
#include<new>
#include<memory>
void fun(int* ptr)
{
std::cout << "void fun()\n";
delete ptr;
}
int main(int argc, char *argv[])
{
//std::unique_ptr<int> x(new int(3), fun); //fun与unique_ptr的第二个缺省实参类型不匹配
std::unique_ptr<int,decltype(&fun)> x(new int(3), fun); //使用decltype(&fun);让编译器自动推导其类型
std::cout << x.get() << std::endl;
return 0;
}
● weak_ptr——防止循环引用而引入的智能指针
struct Str
{
std::shared_ptr<Str> m_mei;
~Str()
{
std::cout << "Str::~Str()\n";
}
};
int main(int argc, char *argv[])
{
std::shared_ptr<Str> x(new Str{}); //x包含的内存的引用计数为1
std::shared_ptr<Str> y(new Str{}); //y包含的内存的引用计数为1
x->m_mei = y; //y的引用计数由1变为2,因为x也引用了y包含的内存
y->m_mei = x;//x的引用计数由1变为2,因为y也引用了x包含的内存
//程序运行到这里y(x也是一样的)的引用计数由2变为1,不调用析构函数
//程序结束之后并没有打印Str::~Str(),说明构造的对象并没有被销毁
return 0;
}
struct Str
{
std::shared_ptr<Str> m_mei;
~Str()
{
std::cout << "Str::~Str()\n";
}
};
int main(int argc, char *argv[])
{
std::shared_ptr<Str> x(new Str{});
std::shared_ptr<Str> y(new Str{});
x->m_mei = y;
//y->m_mei = x; //如果注掉这句话就OK
return 0;
}
#include<iostream>
#include<new>
#include<memory>
struct Str
{
std::shared_ptr<Str> m_mei;
~Str()
{
std::cout << "Str::~Str()\n";
}
};
int main(int argc, char *argv[])
{
std::shared_ptr<Str> x(new Str{});
//std::shared_ptr<Str> y(new Str{});
x->m_mei = x; //自己给自己赋值也会造成循环引用
return 0;
}
– 基于 shared_ptr 构造
#include<iostream>
#include<new>
#include<memory>
struct Str
{
std::weak_ptr<Str> m_mei;
~Str()
{
std::cout << "Str::~Str()\n";
}
};
int main(int argc, char *argv[])
{
std::shared_ptr<Str> x(new Str{}); //weak_ptr可以和shared_ptr共享一块内存,但weak_ptr在构造的时候并不会增加shared_ptr的引用计数
std::shared_ptr<Str> y(new Str{});
x->m_mei = y; //可以使用一个shared_ptr初始化一个weak_ptr,初始化后并不会增加y的引用计数的值
y->m_mei = x; //x和y内的引用计数都是1
return 0;
}
– lock 方法
struct Str
{
std::weak_ptr<Str> m_mei;
~Str()
{
std::cout << "Str::~Str()\n";
}
};
int main(int argc, char *argv[])
{
std::shared_ptr<Str> x(new Str{});
std::shared_ptr<Str> y(new Str{});
x->m_mei = y;
y->m_mei = x;
auto ptr = x->m_mei.lock(); //ptr指向一个有效的shared_ptr
if (ptr)
{
std::cout << "Can access pointer\n";
}
else
{
std::cout << "Cannot access pointer\n";
}
return 0;
}
参考
深蓝学院:C++基础与深度解析