C++ Primer : 第十二章 : 动态内存之unique_ptr和weak_ptr

unique_ptr

一个unique_ptr拥有它所管理的对象,与shared_ptr不同,unique_ptr指向的对象只能有一个用户。当unique_ptr被销毁后,它所指向的对象也被销毁。

定义一个unique_ptr时,需要将其绑定到一个new返回的指针上,类似shared_ptr,初始化unique_ptr必须采用直接初始化形式:
unique_ptr<double> p1; // p1指向一个double类型变量
unique_ptr<int> p2(new int(1024)); // p2指向一个int类型,值为1024


因为只有一个unique_ptr拥有它所指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。

unique_ptr操作
unique_ptr<T> u1 空unique_ptr,可以指向类型为T的对象,u1会使用delete来释放它的指针
unique_ptr<T, D> u2 u2会使用D来释放它的指针
unique_ptr<T, D> u(d) 空unique_ptr,指向类型为T的对象,用类型为D的对象 d 代替delete
u = nullptr 释放u指向的对象,将u置为空
u.release() u放弃对指针的控制权,返回指针,并将u置空
u.reset() 释放u指向的对象
u.reset(q) 提供了内置指针q,领u指向这个对象
u.reset(nullptr) 将u置为空



我们不能拷贝一个unique_ptr,但是我们可以用release函数将指针的控制权从一个(非const) unique_ptr转移给另一个unique_ptr
unique_ptr<int> p1(new int(1024));
unique_ptr<int> p2(p1.release()); // 控制权由p1转交给p2,p1置为空

release成员返回unique_ptr当前保存的指针并将其置为空。

reset成员函数接受一个可选的指针参数,令unique_ptr重新指向给定的指针。如果unique_ptr指向的对象不为空,则它原来指向的对象被释放。

  • 传递unique_ptr和返回unique_ptr

虽然不能拷贝一个unique_ptr,但是有一个例外,我们可以拷贝或赋值一个将要被销毁的unique_ptr,比如返回一个unique_ptr
unique_ptr<int> clone(int p) {

    return unique_ptr<int>(new int(p));

}

还可以返回一个局部变量的拷贝:

unique_ptr<int> clone(int p) {
  
    unique_ptr<int> ret(new int(p));
    return ret;

}


  • 向unique_ptr传递删除器

unique_ptr默认使用delete来释放指针指向的对象。我们也可以重载这个函数,提供我们自己的删除器,但unique_ptr管理删除器的方式与shared_ptr不同,我们在第16章学到。
我们重写连接程序,用unique_ptr代替shared_ptr:
void f (destination& d) {

    connection c = connection(&d);
    unique_ptr<connection, decltype(end_connection)*> p(&d, end_connection);
    // 使用连接
    // 当f退出时,即使是由于异常退出,connection也会被正常关闭
}




weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到shared_ptr,不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向这个对象,对象还是会被释放。
weak_ptr操作
weak_ptr<T> w 空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp) 与shared_ptr  sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型
w = p p可以是一个shared_ptr或weak_ptr,赋值后w与p共享对象
w.reset() 将w置为空
w.use_count() 返回w共享对象额shared_ptr的数量
w.expried() 若w.use_count()为0, 返回true,表示已经失效,否则返回false
w.lock() 如果expired为true,返回一个空shared_ptr,否则返回一个指向w的对象的shared_ptr




当我们创建一个weak_ptr时,需要用shared_ptr来初始化:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp与p共享对象,但p的引用计数未改变

由于不能确定对象是否存在,所以不能用一个weak_ptr直接访问对象,而是使用lock成员函数
if (shared_ptr<int> np = wp.lock())
{
    /* np与wp共享对象 */
}

如果对象存在,则wp.lock()返回一个wp共享对象的shared_ptr,否则返回一个空的shared_ptr。


  • weak_ptr的一个例子
我们前面讲到了StrBlob类,用来管理string的类,因为要实现公用数据,因此使用了shared_ptr来实现,我们作为一个weak_ptr的例子,来实现一个StrBlob类的伴随类StrBlobPtr,来阻止用户访问一个不存在的vector。

class StrBlobPtr {

public:
	StrBlobPtr() : curr(0){}
	StrBlobPtr(StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz){}
	
	std::string& deref(); // 解引用
	StrBlobPtr& incr();   // 前缀递增

private:
	
	std::shared_ptr<std::vector<std::string>> check(size_t, const std::string&) const;
	std::weak_ptr<std::vector<std::string>> wptr;
	size_t curr;
};

wptr或者为空,或者指向一个StrBlob的data成员;curr表示当前的下标。

std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(size_t i, const std::string& msg) const {
	
	auto ret = wptr.lock();
	if (!ret)
		throw std::runtime_error("unbound StrBlobPtr");
		
	if (i >= ret.size())
		throw std::out_of_range(msg);

	return ret;	
}

std::string& StrBlobPtr::deref() const{
    
	auto p = check(curr, "dereference past end");
	return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::incr() {
	
	check(curr, "increment past end of StrBlobPtr");
	++curr;
	return *this;
}

check函数先获取StrBlob对象中的data成员,如果存在,则进行下标判断,不存在的话直接抛除一个运行时错误来阻止用户继续访问不存在的数据; deref函数来解引用当前下标,来获得当前下标的string,incr函数则时将当前下边curr递增。

为了使StrBlobPtr的成员能访问StrBlob的私有数据,则需要将StrBlobPtr定义为StrBlob的友元类,我们还定义了两个函数begin和end,分别返回StrBlob的首元素和尾后元素:

class StrBlobPtr;
class StrBlob {
	
	friend class StrBlobPtr;
	
	StrBlobPtr begin() { return StrBlobPtr(*this); }
	StrBlobPtr end() {
		auto ret = StrBlobPtr(*this, data->size());
		return ret;
	}
	
};



原文地址:https://www.cnblogs.com/averson/p/5096060.html