3.智能指针之unique_ptr

3.智能指针之unique_ptr

一、unique_ptr用来代替auto_ptr的。

通常情况下函数的运行分为以下几个步骤:

1. 加载资源

2. 进行部分操作

3. 释放资源

考虑下面这个情况:

void fun()
{
    int *a = new int(1000);
    //do something
    delete a;
}

如果在do something过程中函数被return或者  函数在运行期前抛出一个异常,那么delete a;则没有被执行,也就造成了内存泄漏。

为了解决该问题c++标准库引进了unique_ptr

unique_ptr是根据“exclusive ownership”(专属所有权)设计的,专属所有权就是确保了在同一时间一个指针拥有一个对象的全部资源。

unique_ptr拥有与普通指针相似的接口,但是没有指针算法,例如p++则是不允许的。

上面的函数可以更改为下面形式:

void fun()
{
     unique_ptr<int> a(new int(1000));
     //do something 
}

当使用unique_ptr的时候,不能使用赋值初始化。

unique_ptr<string>p1 = new string(“string”);//错误
unique_ptr<int>p2(new int(100));

unique_ptr不是必须拥有一个对象,所以它可以是空。

std::unique_ptr<std::string>p;//p将会被默认构造函数初始化

当然

p = nullptr;p.reset();会产生相同的结果,此时p都为空。

我们可以这样去检测p是否拥有一个对象资源(因为unique_ptr 中重载了 bool());

if(p)
{
    ...
}

我们也可以这样去检测:

if(p == nullptr)
{
    ...
}
if(p.get() == nullptr)
{
    ...
}

release()函数获得unique_ptr中的资源对象。

std::string *p2 = p.release();//p == nullptr

unique_ptr转让所有权,unique_ptr提供了“exclusive ownership”语义,这是为了确保两个对象不会被同一个指针初始化。

std::string* sp = new std::string("hello");
std::unique_ptr<std::string> up1(sp);
std::unique_ptr<std::string> up2(sp);//程序异常。 up1 and up2 own same data

这是一个运行时错误,所以我们必须在逻辑上避免该问题。

我们不能对齐使用普通的赋值语句以及copy构造函数,但是可以使用move语义。

例如:

std::unique_ptr<string> up1(new string);
std::unique_ptr<string> up2(up1); //错误,无法通过编译
std::unique_ptr<string> up3(std::move(up1));//ok,move语义

同理:

std::unique_ptr<string> up1(new string);
std::unique_ptr<string> up2; // create another unique_ptr
up2 = up1; // ERROR: not possible
up2 = std::move(up1);

看下面这几行代码:

std::unique_ptr<ClassA> up1(new ClassA);
std::unique_ptr<ClassA> up2(new ClassA);
up2 = std::move(up1);

此时up2的的析构函数将会被调用,因为当使用move语义为up2赋值时,up2失去了原有对象的所有权,所以此时原有对象会被delete

注意:如果要为unique_ptr分配一个新的对象,那么该对象必须是一个unique_ptr对象,而不能是一个普通指针。

ptr = new ClassA; // ERROR
ptr = std::unique_ptr<ClassA>(new ClassA); // OK, delete old object

而为ptr分配一个nullptr也是允许的。

ptr = nullptr;//等同于调用ptr.reset();

有两种情况函数需要用到move语义。

1. unique_ptr作为一个参数传递给一个函数,例如:

void fun(unique_ptr<string>str)
{
	cout << *str;
}
...
unique_ptr<string>str(new string("hello world"));
fun(unique_ptr<string>(new string ("test")));
fun(std::move(str));
...

注意:调用fun(unique_ptr<string>str)函数时,参数一定要是右值。

2. 在函数中生成unique_ptr并返回

unique_ptr<string, void(*)(string*)>fun()
{
	unique_ptr < string, void(*)(string*) > str(new string("1111"), [](string *str)
	{
		cout << *str;
		delete str;
	});
	return str;
}
...
auto it = fun();
...

注意:在这中情况下,不用显示声明move(),因为根据C++11标准规定,编译器会尝试使用move语义。

二、unique_ptr做为成员变量

考虑下面这种情况:


class ClassB {
private:
	ClassA* ptr1; // pointer members
	ClassA* ptr2;
public:
	// constructor that initializes the pointers
	// - will cause resource leak if second new throws
	ClassB(int val1, int val2)
		: ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
	}
	// copy constructor
	// - might cause resource leak if second new throws
	ClassB(const ClassB& x)
		: ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
	}
	// assignment operator
	const ClassB& operator= (const ClassB& x) {
		*ptr1 = *x.ptr1;
		*ptr2 = *x.ptr2;
		return *this;
	}
	~ClassB() {
		delete ptr1;
		delete ptr2;
	}
	...
};

如果在初始化ptr2时抛出一个异常,那么将会造成ptr1的内存泄漏,为了避免该问题的发生,我们可以将代码中的普通指针更改为unique_ptr。

class ClassB {
private:
std::unique_ptr<ClassA> ptr1; // unique_ptr members
std::unique_ptr<ClassA> ptr2;
public:
// constructor that initializes the unique_ptrs
// - no resource leak possible
ClassB (int val1, int val2)
: ptr1(new ClassA(val1)), ptr2(new ClassA(val2)) {
}
// copy constructor
// - no resource leak possible
ClassB (const ClassB& x)
: ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2)) {
}
// assignment operator
const ClassB& operator= (const ClassB& x) {
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
// no destructor necessary
// (default destructor lets ptr1 and ptr2 delete their objects)
...
};

这里我们的ClassA也需要实现copy 构造函数以及赋值操作,否则将只会调用move构造函数

三、操作数组
std::unique_ptr<std::string> up(new std::string[10]); // runtime ERROR

但是unique_ptr与shared_ptr有所区别,我们可以直接声明为

std::unique_ptr<std::string[]> up1(new std::string[10]);//ok
std::cout << *up << std::endl; // ERROR: * not defined for arrays
std::cout << up[0] << std::endl; // OK,此时我们应该确保下标不能越界

接下来我们看下在unique_ptr内部中关于使用数组声明以及普通参数的区别。

namespace std {
// primary template:
template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
...
T& operator*() const;
T* operator->() const noexcept;
...
};
// partial specialization for arrays:
template<typename T, typename D>
class unique_ptr<T[], D>
{
public:
...
T& operator[](size_t i) const;
...
}
}

当然与之对应的还有两个版本的default_delete

namespace std {
// primary template:
template <typename T> class default_delete {
public:
void operator()(T* p) const; // calls delete p
...
};
// partial specialization for arrays:
template <typename T> class default_delete<T[]> {
public:
void operator()(T* p) const; // calls delete[] p
...
};
}

通过源码,我们就不难理解下面两种情况为什么第一种会发生异常了。

std::unique_ptr<std::string> up1(new std::string[10]);//error
std::unique_ptr<std::string[]> up1(new std::string[10]);//ok

四、自定义删除器

在某些情况下default_delete 与 default_delete[]无法满足我们的要求,此时我们需要定义我们自己的删除器,而我们在这里定义的删除器与在shared_ptr中的定义方式又有一些不同,在unique_ptr中我们必须将删除器的类型,作为模板的第二个参数声明,这个参数可以是函数引用,函数指针或者一个函数对象。

例如:

class ClassADeleter
	{
	public:
		void operator () (ClassA* p) {
			std::cout << "call delete for ClassA object" << std::endl;
			delete p;
		}
	};...
std::unique_ptr<ClassA,ClassADeleter> up(new ClassA());

当然也可以使用一个函数或者一个lambda表达式,但是也需要声明这个函数的类型,例如:

std::unique_ptr<int,void(*)(int*)> up(new int[10],
[](int* p) {
...
delete[] p;
});

或者

auto dl = [](int* p) {
...
delete[] p;
};
std::unique_ptr<int,decltype(dl)>> up(new int[10], dl);

当然,为了避免一定要声明这个函数的类型我们也可以使用一个别名来代替。

template <typename T>
	using uniquePtr = std::unique_ptr<T, void(*)(T*)>; // alias template
	...
		uniquePtr<int> up(new int[10], [](int* p) { // used here
		...
			delete[] p;
	});

注意:

unique_ptr<int>p1(new int);       //OK
unique_ptr<int>p2(new int[10]);   //error可以编译但是程序会崩溃
unique_ptr<int[]>p3(new int[10]); /OK
shared_ptr<int>p4(new int);       //OK
shared_ptr<int>p5(new int[10]);   //可以通过编译但会崩溃
shared_ptr<int[]>p6(new int[10]); //无法通过编译

1. unique_ptr中允许将类型声明为void的,所以void*也是可以的

2. T被声明为T[]时,[]操作将会代替*和->

3. 默认的删除器可以是delete[]和delete两种

4. 指针不能相互转化,不能用父类的指针指向子类。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值