文章目录
1. 模板种类
智能指针模板类 | 支持情况 | 使用场景 |
---|---|---|
auto_ptr | C++98提供的,C++11已摒弃,但如果编译器不支持其它两种C++11提供的,则只能使用auto_ptr | |
unique_ptr | C++11提供 (如果编译器没提供,可使用Boost库的scoped_ptr) | 1. 不需要多个指向同一对象的指针 2. 支持下标法操作内部指针指向的元素 |
shared_ptr | C++11 提供(如果编译器没提供,可使用Boost库的shared_ptr) | 2. 多个指针指向同一个对象,支持复制和赋值操作 2.不支持下标法访问内部指针指向的元素> 注意:需要包含头文件<memory> |
2. 智能指针使用示例
#include <memory>
#include <string>
#include <iostream>
class CReport
{
private:
std::string m_str;
public:
CReport(const std::string &str)
:m_str(str)
{
std::cout << "Object created!!!\n";
}
~CReport()
{
std::cout << "Object deleted!!!\n";
}
void Show() const
{
std::cout << m_str << "\n";
}
};
void Test01()
{
//! 都是模板类,本质就是模板类中维护一个T*指针,通过析构函数来delete T*指针
std::auto_ptr<CReport> pa(new CReport("using auto_ptr"));
pa->Show();
std::shared_ptr<CReport> ps(new CReport("using shared_ptr"));
ps->Show();
std::unique_ptr<CReport> pu(new CReport("using unique_ptr"));
pu->Show();
}
2.1 智能指针初始化
2.1.1 shared_ptr的初始化
如果我们不初始化智能指针,它就会被初始化为一个空指针。
- 使用make_shared函数
#include <memory>
std::shared_ptr<std::string> spStr = std::make_shared<std::string>("harry");
//! spStr用作一个条件判断,若spStr指向一个对象,则为true
if (spStr && spStr->empty())
{
*spStr = "hello world";
}
//! 也可以使用auto来简化变量的声明
auto p6 = std::make_shared<std::string>("hello");
std::cout << *p6 << std::endl;
使用make_shared分配内存失败会抛出异常,示例如下:
std::shared_ptr<std::string> spStr;
try
{
spThrd = std::make_shared<std::string>();
}
catch (std::bad_alloc& ex)
{
cat.info("[%s] 初始化失败,详情:%s", TESTITEM_NAME, ex.what());
return false;
}
catch (...)
{
cat.info("[%s] 初始化失败,未知错误", TESTITEM_NAME);
return false;
}
在类的构造函数初始化列表中初始化智能指针
class CStudent
{
public:
CStudent(int a)
:m_sp(std::make_shared<int>(a)){}
int Get() const {return *m_sp;}
private:
std::shared_ptr<int> m_sp;
};
void Test01()
{
CStudent s(3);
std::cout << s.Get();
}
- 使用构造函数初始化
//! 由于智能指针接受指针参数的构造函数是explicit,所以无法隐士转换。
shared_ptr<int> p1 = new int(1024); //< 错误,不支持隐士转换
shared_ptr<int> p2(new int(42)); //< 正确
- reset函数
通常和unique一起使用,来控制多个shared_ptr共享的对象。在改变底层对象之前,我们检查自己是否当前对象仅有的用户,如果不是,在改变之前要制作一份新的拷贝,避免更改了其它用户的内容
auto p = std::make_shared<int>(5);
auto q(p);
if (!p.unique()) //< 判断是否唯一的用户(引用计数大于1),如果不是则重新分配新的拷贝
{
p.reset(new int(*p)); //< 重新分配新的拷贝,这个时候p所管理的内存就是自己独有的了,可以随意改变,不用担心影响其它用户
}
*p = 15;
2.1.2 unique_ptr的初始化
unique_ptr<string> p1(new string("hello"));
//! 将所有权从p1(指向"hello")转移给p2
unique_ptr<string> p2(p1.release()); //< release将p1置为空
2.2 智能指针移交所有权
2.2.1 unique_ptr移交所有权
unique_ptr<string> p1(new string("world"));
unique_ptr<string> p2(new string("harry"));
//! 将所有权从p1转移给p2,release返回之p1内部所管理对象的指针,并将p1内部的指针置为空
p2.reset(p1.release()); //< reset释放了p2原来指向的内存
3. 有关智能指针的注意事项
- 三种智能指针都应避免
std::string str ("hello");
shared_ptr<std::string> pstr(&str); //< 不允许
// 因为pstr过期时, 析构会delete对象内部维护的std::string指针,而这里str是栈上的内存,不是堆空间的,这里将把delete运算符用于非堆内存,非法操作
- 三种智能指针对赋值语句的策略
- auto_ptr
std::auto_ptr<std::string> pa(new std::string("hello world"));
std::auto_ptr<std::string> pa1;
pa1 = pa; //< 赋值的时候,移交了所有权给pa1
std::cout <<* pa.get(); //< 异常,因为pa交出了所有权,所以pa维护的std::string指针为空,导致以打印的时候会出错
std:: cout << *pa1.get(); //< 正常
- shared_ptr
std::shared_ptr<std::string> pa(new std::string("hello world")); //< 引用计数+1
std::shared_ptr<std::string> pa1; //< 引用计数+1
pa1 = pa; //< 内部维护的std::string指针指向同一个堆空间
std::cout <<*pa.get() << std::endl; //< 正常,没有所有权的概念,采用引用计数的方式,
//< 只有引用计数降为0的时候,析构才delete所维护的std::string指针
std:: cout << *pa1.get(); //< 正常
- unique_ptr
//! unique_ptr也是使用所有权模型,与auto_ptr一样,但unique_ptr赋值会在编译阶段就报错
std::unique_ptr<std::string> pa(new std::string("hello world"));
std::unique_ptr<std::string> pa1;
pa1 = pa; //< 这里在编译阶段就报错
std::cout <<*pa.get() << std::endl;
std:: cout << *pa1.get();
- C++ Primer 12.1.4 智能指针和异常
4. unique_ptr优于auto_ptr
- 编译器阶段错误比潜在的程序崩溃更安全
std::unique_ptr<std::string> pa(new std::string("hello world"));
std::unique_ptr<std::string> pa1;
pa1 = pa; //< 这里在编译阶段就报错,但如果使用auto_ptr,则编译不会报错,
//< 使用pa内部维护的std::string指针时才会出错
- 允许赋值的情况
程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;但如果源unique_ptr将存在一段时间,编译器将禁止这样做。如下代码示例:
std::unique_ptr<std::string> pu(new std::string("hello world"));
std::unique_ptr<std::string> pu1;
// pu1 = pu; //< 编译器禁止这么做,因为pu长期存在,可能会存在使pu中的std::string对象的情况
std::unique_ptr<std::string> pu3;
pu3 = std::unique_ptr<std::string>(new std::string("hello world")); //< 允许,因为右边生成的临时对象,再把std::string对象的所有权移交给pu3后,会很快被销毁,没有机会使用它来访问无效的数据
//! 函数的返回值,也是临时右值,可以这么使用,当把临时右值赋值给接收者变量std::unique_ptr后,
//! 转移了所有权,临时右值会被销毁
std::unique_ptr<std::string> Get()
{
std::unique_ptr<std::string> pu(new std::string("hello"));
return pu;
}
- 可用于数组的变体
使用new分配内存——可以使用auto_ptr、shared_ptr和unique_ptr
使用new[]分配内存——可以使用unique_ptr
5. unique_ptr转为shared_ptr
模板shared_ptr包含一个显示构造函数,可将右值unique_ptr转换为shared_ptr,share_ptr将接管原来归unique_ptr所有的对象
std::unique_ptr<int> GetUni(int n)
{
return std::unique_ptr<int>(new int(n));
}
int main()
{
std::unique_ptr<int> pu(GetUni(3)); //< 正确,pu构造函数内为临时的unique_ptr<int>对象
std::shared_ptr<int> ps(pu); //< 报错,编译器不允许右值为非临时变量
std::shared_ptr<int> puts(GetUni(3)); //< 正确,将临时的unique_ptr<int>对象转为shared_ptr<int>对象
return 0;
}
6. 参考书籍
C++ Primer Plus(第6版)——16.2 智能指针模板类
7. 智能指针指定删除器
7.1 shared_ptr
将回调函数传递给 shared_ptr 的构造函数,该构造函数将从其析构函数中调用以进行删除
7.1.1 方式一、使用普通函数
// 自定义删除器
void deleter(Sample * x)
{
std::cout << "Deleter function called" << std::endl;
delete[] x;
}
// 构造函数传递自定义删除器指针
std::shared_ptr<Sample> p1(new Sample[5], deleter);
7.1.2 方式二、使用仿函数
class Deleter
{
public:
void operator() (Sample *x) {
std::cout << "Deleter function called" << std::endl;
delete[] x;
}
};
// 构造函数传递自定义删除器指针
std::shared_ptr<Sample> p2(new Sample[5], Deleter);
7.1.3 方式三、使用lambda表达式
std::shared_ptr<Sample> p3(new Sample[5], [](Sample *x) {
std::cout << "Deleter function called" << std::endl;
delete[] x;
});
7.1.4 方式四、使用std::default_delete
std::shared_ptr<Sample> p(new Sample[5], std::default_delete<Sample[]>());
7.2 unique_ptr
7.2.1 普通函数
需要使用decltype关键字
void Deleter(std::string* pstr)
{
delete pstr;
pstr = nullptr;
}
void Test01()
{
//!
std::unique_ptr<std::string, decltype(Deleter)*> up(new std::string("hello"), Deleter);
}
8. 规避问题
8.1 以独立的语句将newed对象置入智能指针
effective c++ 条款17 (第三版)
//! 假如如下函数
int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);
//! 调用
processWidget(std::shared_ptr<Widget>(new Widget), priority());
- 现象:编译器产出一个processWidget调用码之前,必须首先核算即将被传递的各个实参。也就是会做以下三件事:调用priority(), 执行new Widget, 调用std::shared_ptr构造函数。
- 分析:但c++ 编译器对参数核算的顺序弹性很大,不确定哪个在前哪个在后。所以可能会产生1. 执行new widget;2. 调用priority();3. 调用std::shared_ptr()构造函数,假如priority调用导致异常,则new Widget返回的指针将会遗失,因为它尚未被置入std::shared_ptr内,所以导致资源泄漏。
- 解决措施:
使用分离语句,分别写出(1)创建Widget, (2)将它置入一个只能指针内,再把那个智能指针传给processWidget函数,如下:
std::shared_ptr<Widget> pw(new Widget); //< 在单独语句内以只能指针存储newed所得的对象
processWidget(pw, priority()); //< 这个调用动作绝不至于造成泄漏
以上之所以可以,因为编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内它才拥有那个自由度)