unique_ptr
一个 unique_ptr ”拥有“ 它所指向的对象。某个时刻只能有一个 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 置空
// reset 会释放 u 指向的对象
u.reset();
u.reset(q); // 如果提供了内置指针 q,令 u 指向这个对象;否则将 u 置空
u.reset(nullptr);
与 shared_ptr 不同,没有类似 make_shared 的标准库函数返回一个 unique_ptr。当我们定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上,而且初始化只能够采用直接初始化:
unique_ptr<double> p1; // 可以指向一个 double 的 unique_ptr
unique_ptr<int> p2(new int(42)); // p2 指向一个值为 42 的 int
unique_ptr 拥有它所指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作:
unique_ptr<string> p1(new string("Stegosautus"));
unique_ptr<string> p2(p1); // 错误,unique_ptr 不支持拷贝
unique_ptr<string> p3;
p3 = p1; // 错误,unique_ptr 不支持赋值
虽然不能拷贝或赋值 unique_ptr,但是可以通过调用 release 或 reset 将指针的所有权从一个 (非 const) unique_ptr 转移给另一个 unique_ptr:
// 将所有权从 p1 转移给 p2
unique_ptr<string> p2(p1.release()); // release 将 p1 置为空
unique_ptr<string> p3(new string("Trex"));
// 将所有权从 p3 转移给 p2
p2.reset(p3.release()); // reset 释放了 p2 原来指向的内存
调用 release 会切断 unique_ptr 和它原来管理的对象的联系。release 返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。如果我们不用另一个智能指针来保存 release 返回的指针,我们的程序就要负责资源的释放。
p2.release(); // 错误:p2 不会释放内存,而且我们丢失了 p2 指针
auto p = p2.release(); // 正确,**但必须记得 delete(p)**
传递 unique_ptr 参数和返回 unique_ptr
不能拷贝 unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的 unique_ptr。最常见的就是从函数返回一个 unique_ptr:
unique_ptr<int> clone(int p) {
// 正确:从 int* 创建一个 unique_ptr<int>
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 释放它所指向的对象。与 shared_ptr 同,我们可以重载一个 unique_ptr 的默认删除器。但 unique_ptr 与 shared_ptr 管理删除器的方式不同。
我们在尖括号中 unique_ptr 指向类型之后提供删除器类型。在创建或 reset 一个这种 unique_ptr 类型的对象时,必须提供一个指定类型的可调用对象。 如:
// p 指向一个类型为 objT 的对象,并使用一个类型为 delT 的对象释放 objT 对象
// 它会调用一个名为 fcn 的 delT 类型对象
unique_ptr<objT, delT> p(new objT, fcn);
更具体的,假设我们有一个类 connection,以及用来释放这个类的函数 end_connection,我们用 unique_ptr 来管理时,应该这样:
unique_ptr<connection, decltype(end_connection)*> p(new connect(arg),end_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.expired(); // 若 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 指向相同的对象。但创建 wp 并没有改变 p 的引用计数。
由于对象可能不存在,我们不能直接使用 weak_ptr 直接访问对象,必须调用 lock。同样,lock 如果返回的不是空 shared_ptr,同样会增加引用计数。也就是说,只要此 shared_ptr 存在,它所指向的底层对象也会一直存在。
核查指针类
作为 weak_ptr 用途的一个展示,我们将 StrBlob 类定义一个伴随指针类。其类名为 StrBlokPtr,会保存一个 weak_ptr,指向 StrBlob 的 data 成员,这是初始化时提供给它的。通过使用 weak_ptr,不会影响一个给定的 StrBlob 所指向的 vector 的生存期。但是可以阻止用户访问一个不再存在的 vector。
StrBlobPtr 有两个数据成员:wptr,为空或者指向一个 StrBlob 中的 vector;curr,保存当前对象所表示的元素的下标。类似 StrBlob,此类也有一个 check 成员来检查解引用 StrBlobPtr 是否安全:
// 对于访问一个不存在元素的尝试,StrBlobPtr 抛出一个异常
class StrBlobPtr {
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0) :
wptr(a.data), curr(sz) {}
std::string &deref() const; // 模仿解引用运算符
StrBlobPtr &incr(); // 模仿前缀递增运算符
private:
// 若检查成功,check 返回一个指向 vector 的 shared_ptr
std::shared_ptr<std::vector<std::string>>
check(std::size_t, const std::string&) const;
// 保存一个 weak_ptr,意味着底层 vector 可能被销毁
std::weak_ptr<std::vector<std::string>> wptr;
std::size_t curr; // 在数组中的当前位置
};
需要注意的是,我们不能将 StrBlobPtr 绑定到一个 const StrBlob 对象上,因为构造函数接受的是一个非 const StrBlob 对象。
StrBlobPtr 中的 check 成员与 StrBlob 中的同名成员不同,它还需要检查指针指向的 vector 是否还存在:
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::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; // 如果 vector 仍然存在,便返回指向 vector 的 shared_ptr
}
指针操作
这里,我们将定义名为 deref 和 incr 的函数用来解引用和递增 StrBlobPtr。
std::string& StrBlobPtr::deref() const {
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
StrBlobPtr& StrBlobPtr::incr() {
// 如果 curr 已经指向容器的尾后位置,就不能递增它
check(curr,"increment past end of StrBlobPtr");
++ curr;
return *this;
}
当然,我们需要在 StrBlob 将 StrBlobPtr 声明为友元类。以及为 StrBlob 定义 begin 与 end 操作,返回一个指向它自身的 StrBlobPtr:
class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr;
// 其他成员与之前同
};
// 中间是 StrBlobPtr 类代码
StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end() {
auto ret = StrBlobPtr(*this,data -> size());
return ret;
}
练习 12.20
/*
* My_StrBlob.h
*/
#include "memory"
#include "vector"
#include "string"
#include "stdexcept"
class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr;
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> i1);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// 添加和删除元素
void push_back(const std::string &t) { data->push_back(t); }
void pop_back();
// 元素访问
std::string &front();
std::string &back();
const std::string &front() const;
const std::string &back() const;
StrBlobPtr begin();
StrBlobPtr end();
private:
std::shared_ptr<std::vector<std::string>> data;
// 如果 data[i] 不合法,抛出一个异常
void check(size_type i, const std::string &msg) const;
};
// 对于访问一个不存在元素的尝试,StrBlobPtr 抛出一个异常
class StrBlobPtr {
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob &a, size_t sz = 0) :
wptr(a.data), curr(sz) {}
std::string &deref() const;
StrBlobPtr &incr(); // 前缀递增
bool operator != (const StrBlobPtr &x) const { // 同类型函数代替也是一样的
return !(wptr.lock() == x.wptr.lock() && x.curr == curr);
}
private:
// 若检查成功,check 返回一个指向 vector 的 shared_ptr
std::shared_ptr<std::vector<std::string>>
check(std::size_t, const std::string &) const;
// 保存一个 weak_ptr,意味着底层 vector 可能被销毁
std::weak_ptr<std::vector<std::string>> wptr;
std::size_t curr; // 在数组中的当前位置
};
StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) {}
StrBlob::StrBlob(std::initializer_list<std::string> i1) :
data(std::make_shared<std::vector<std::string>>(i1)) {}
void StrBlob::check(StrBlob::size_type i, const std::string &msg) const {
if (i >= data->size()) {
throw std::out_of_range(msg);
}
}
std::string &StrBlob::front() {
check(0, "front on empty StrBlob");
return data->front();
}
std::string &StrBlob::back() {
check(0, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back() {
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
const std::string &StrBlob::front() const {
check(0, "front on empty StrBlob");
return data->front();
}
const std::string &StrBlob::back() const {
check(0, "back on empty StrBlob");
return data->back();
}
StrBlobPtr StrBlob::begin() {
return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end() {
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::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; // 如果 vector 仍然存在,便返回指向 vector 的 shared_ptr
}
std::string &StrBlobPtr::deref() const {
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
StrBlobPtr& StrBlobPtr::incr() {
// 如果 curr 已经指向容器的尾后位置,就不能递增它
check(curr,"increment past end of StrBlobPtr");
++ curr;
return *this;
}
/*
* main.cc
*/
#include <fstream>
#include <iostream>
#include "My_StrBlob.h"
int main() {
std::ifstream fin("in.txt");
StrBlob x;
std::string s;
while(getline(fin,s)) {
x.push_back(s);
}
StrBlobPtr be = x.begin(),en = x.end();
while(be != en) {
std::cout << be.deref() << std::endl;
be.incr();
}
return 0;
}