为什么要用智能指针呢?
因为裸指针存在很多问题,主要是下面这些:
难以区分指向的是单个对象还是一个数组;
使用完指针之后无法判断是否应该销毁指针,因为无法判断指针是否“拥有”指向的对象;
在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是有其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针;
即便已经确定了销毁指针的方法,由于1的原因,仍然无法确定到底是用delete(销毁单个对象)还是delete[](销毁一个数组);
假设上述的问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针操作;任何一条路径遗漏都可能导致内存泄露,而销毁多次则会导致未定义行为;
理论上没有方法来分辨一个指针是否处于悬挂状态;
智能指针
智能指针主要是下面几个,c++11之后就引入到标准库成为语言新特性了(之前是在boost库中)
std::auto_ptr, std::unique_ptr
std::shared_ptr, std::weak_ptr
上述四个智能指针类型中,auto_ptr是c++ 98遗留的关键字,已经不建议使用,auto_ptr的功能都可以由unique_ptr更加高效的做到;本文主要先讲一讲unique_ptr;
unique_ptr在要表达“专属所有权”的语义时使用,即unique_ptr指针永远“拥有”其指向的对象,所以unique_ptr是一个move-only类型,一个unique_ptr指针是无法被复制的,只能将“所有权”在两个unique_ptr指针之间转移,转移完成后源unique_ptr将被设为null;
定义一个unique_ptr并指向一块动态内存:
std::unique_ptr<int> pInt(nullptr);
pInt.reset(new int(1));
std::cout << *pInt << "\n"; //解引用操作与裸指针相同,打印的结果为1
unique_ptr一个常用的场景是在使用工厂模式的时候,在工厂方法中返回一个unique_ptr类型的指针,例如
#pragma once
//investment.h
#include <assert.h>
#include <memory>
#include <iostream>
#include <functional>
//类定义
class Investment {
public:
virtual ~Investment() {
std::cout << "investment destoryed\n";
}
};
void makeLogEntry(Investment* pInv) {
std::cout << "deleting investment on " << pInv << "\n";
}
class Stock : public Investment {
public:
Stock() {
std::cout << "make an invesetment on stock\n";
}
virtual ~Stock() {
std::cout << "a stock investment destoryed,";
}
};
class Bond : public Investment {
public:
Bond() {
std::cout << "make an investmentt on bond\n";
}
virtual ~Bond() {
std::cout << "a bond investment destroyed,";
}
};
class RealEstate : public Investment {
public:
RealEstate() {
std::cout << "make an investmentt on RealEstate\n";
}
virtual ~RealEstate() {
std::cout << "a RealEstatend investment destroyed,";
}
};
void deleteAndLog(Investment* pInv) {
makeLogEntry(pInv);
delete pInv;
}
template<typename T, typename... Ts>
static auto makeInvestment(Ts&&... params) {
auto delInvmt = [](Investment* pInv)
{
makeLogEntry(pInv);
delete pInv;
};
typedef std::unique_ptr<Investment, decltype(delInvmt)> InvestmentPtr;
std::cout << sizeof(InvestmentPtr) << "\n";
InvestmentPtr pInv(nullptr, delInvmt);
pInv.reset(new T(std::forward<Ts>(params)...));//不能直接将裸指针赋值给一个unique_ptr,要使用reset
return pInv;
}
客户端的调用方法
auto pInvestment = InvestmentMaker::makeInvestment<Stock>();
unique_ptr默认的销毁方式是通过对unique_ptr中的裸指针进行delete操作,但是也可以在声明的时候指定销毁函数,在上面的代码中,通过lambda表达式置顶了一个打印日志函数,要在销毁指针的时候会打印日志,
template<typename T, typename... Ts>
static auto makeInvestment(Ts&&... params) {
auto delInvmt = [](Investment* pInv)
{
makeLogEntry(pInv);
delete pInv;
};
typedef std::unique_ptr<Investment, decltype(delInvmt)> InvestmentPtr;
InvestmentPtr pInv(nullptr, delInvmt);
pInv.reset(new T(std::forward<Ts>(params)...));//不能直接将裸指针赋值给一个unique_ptr,要使用reset
return pInv;
}
这里使用了c++ 14支持auto函数返回类型推导的特性,如果是c++11的话就需要把lambda表达式写到makeInvestment方法外面了。
在使用默认delete操作的时候,unique_ptr的内存size与裸指针一致,而使用自定义销毁方式的时候,unique_ptr的size取决于自定义销毁的方式:
在使用函数指针的时候,unique_ptr增加一个或两个字长;
使用函数对象的时候,unique_ptr的size取决于函数对象中的语句数量;
使用不带capture的lambda表达式时,不增加size
void deleteAndLog(Investment* pInv) {
makeLogEntry(pInv);
delete pInv;
}
template<typename T, typename... Ts>
static auto makeInvestment(Ts&&... params) {
auto delInvmt = [](Investment* pInv)
{
makeLogEntry(pInv);
delete pInv;
};
typedef std::unique_ptr<Investment, decltype(delInvmt)> InvestmentPtr;
std::cout << sizeof(InvestmentPtr) << "\n"; //lambda表达式不增加size, size为4
typedef std::unique_ptr<Investment, decltype(&deleteAndLog)> FunPtrInvestmentPtr;
std::cout << sizeof(FunPtrInvestmentPtr) << "\n"; //函数指针增加1一个字长, size为8
std::function<void(Investment*)> funcionObj = deleteAndLog;
typedef std::unique_ptr<Investment, decltype(funcionObj)> FunObjInvestmentPtr;
std::cout << sizeof(FunObjInvestmentPtr) << "\n"; //函数对象增加大小取决于函数体的语句数量,这里的size为48
//...
}
unique_ptr是c++11之后用来表示专属所有权的一种智能指针,除了上面讲到的这些特性之外,另外还有一个非常方便的特性就是可以无缝地转换成shared_ptr,以上面的代码为例子,在调用工厂方法的时候,可以直接赋值给一个shared_ptr指针
std::shared_ptr<Investment> pInvestment = makeInvestment<Stock>();
shared_ptr将在下一篇博客中来讲
unique_ptr总结
unique_ptr是一种内存占用少、速度快、move-only的一种智能指针,用来管理“专属所有”语义下的动态资源;
默认的销毁方式是通过delete,但也可以自定义销毁方式,根据自定义销毁方式的不同幅度增加unique_ptr的size
将unique_ptr转化为shared_ptr是非常容易的;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。