## 0.回顾
前面一节编写了一个RAII的例子:
```cpp
class shape_wrapper {
public:
explicit shape_wrapper(
shape* ptr = nullptr)
: ptr_(ptr) {}
~shape_wrapper()
{
delete ptr_;
}
shape* get() const { return ptr_; }
private:
shape* ptr_;
};
```
这个类可以完成智能指针的最基本的功能:对超出作用域的对象进行释放。但它缺了点东
西:
- 这个类只适用于 shape 类
- 该类对象的行为不够像指针
- 拷贝该类对象会引发程序行为
## 1.手写auto_ptr与scope_ptr
针对**"这个类只适用于 shape 类"**,我们想到了模板,于是改造为:
```cpp
template <typename T>
class smater_ptr {
public:
explicit smater_ptr(
T* ptr = nullptr)
: ptr_(ptr) {}
~smater_ptr()
{
delete ptr_;
}
T* get() const { return ptr_; }
private:
T* ptr_;
};
```
针对**"该类对象的行为不够像指针"**,我们想到了指针的基本操作有`*`,`->`,布尔表达式。
于是添加三个成员函数:
```cpp
template <typename T>
class smater_ptr {
public:
...
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
...
private:
T* ptr_;
};
```
针对**"拷贝该类对象会引发程序行为"**,我们想到了拷贝构造和赋值。
现考虑如下调用:
```cpp
smart_ptr<shape> ptr1{create_shape(shape_type::circle)};
smart_ptr<shape> ptr2{ptr1};
```
对于第二行,究竟应当让编译时发生错误,还是可以有一个更合理的行为?我们来逐一检查
一下各种可能性。
最简单的情况显然是禁止拷贝。我们可以使用下面的代码:
```cpp
template <typename T>
class smart_ptr {
…
smart_ptr(const smart_ptr&)
= delete;
smart_ptr& operator=(const smart_ptr&)
= delete;
…
};
```
当然,也可以设为private。
禁用这两个函数非常简单,但却解决了一种可能出错的情况。否则,`smart_ptr<shape> ptr2{ptr1};` 在编译时不会出错,但在运行时却会有未定义行为——**由于会对同一内存释放两次,通常情况下会导致程序崩溃。**
我们是不是可以考虑在拷贝智能指针时把对象拷贝一份?不行,通常人们不会这么用,因为使用智能指针的目的就是要减少对象的拷贝啊。何况,虽然我们的指针类型是 shape,但实际指向的却应该是 circle 或 triangle 之类的对象。在 C++ 里没有像 Java 的clone 方法这样的约定;**一般而言,并没有通用的方法可以通过基类的指针来构造出一个子类的对象来。**
那关键点就来了,**所有权!**,我们可以拷贝时转移指针的所有权!下面实现便是`auto_ptr`的核心实现:
```cpp
template<typename T>
class auto_ptr {
public:
explicit auto_ptr(
T *ptr = nullptr) noexcept
: ptr_(ptr) {}
~auto_ptr() noexcept {
delete ptr_;
}
// 返回值为T&,允许*ptr=10操作
T &operator*() const noexcept { return *ptr_; }
T *operator->() const noexcept { return ptr_; }
operator bool() const noexcept { return ptr_; }
T *get() const noexcept { return ptr_; }
// 拷贝构造,被复制放释放原来指针的所有权,交给复制方
auto_ptr(auto_ptr &other) noexcept {
ptr_ = other.release();
}
// copy and swap
auto_ptr &operator=(auto_ptr &rhs) noexcept {
// auto_ptr tmp(rhs.release());
// tmp.swap(*this);
// s上述两行等价于下面一行
auto_ptr(rhs.release()).swap(*this);
return *this;
}
// 原来的指针释放所有权
T *release() noexcept {
T *ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void swap(auto_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_); // 转移指针所有权
}
private:
T *ptr_;
};
template<typename T>
void swap(auto_ptr<T> &lhs, auto_ptr<T> &rhs) noexcept {
lhs.swap(rhs);
}
int main() {
auto_ptr<shape> ptr1{create_shape(shape_type::circle)};
auto_ptr<shape> ptr2{ptr1};
if (ptr1.get() == nullptr && ptr2.get())
cout << "拷贝构造:ptr1释放了所有权,ptr2获得了所有权" << endl;
ptr1 = ptr1;
auto_ptr<shape> ptr3{create_shape(shape_type::rectangle)};
ptr1 = ptr3;
if (ptr3.get() == nullptr && ptr1.get())
cout << "赋值操作:始终只有一个对象管理一个区块!ptr3释放了所有权,ptr1获得了所有权" << endl;
}
```
上述通过copy-swap技术完成了避免自我赋值与保证了强异常安全!
如果你觉得这个实现还不错的话,那恭喜你,你达到了 C++ 委员会在 1998 年时的水平:上面给出的语义本质上就是 C++98 的 auto_ptr 的定义。如果你觉得这个实现很别扭的话,也恭喜你,因为 C++ 委员会也是这么觉得的:**auto_ptr 在 C++17 时已经被正式从C++ 标准里删除了**。
上面会导致什么问题呢?
看一下输出结果:
```cpp
shape
circle
拷贝构造:ptr1释放了所有权,ptr2获得了所有权
shape
rectangle
赋值操作:始终只有一个对象管理一个区块!ptr3释放了所有权,ptr1获得了所有权
```
shape与circle实在create_shape时候输出的,我们重点关注最后一句话,发现了一个很大的问题:**它的行为会让程序员非常容易犯错。一不小心把它传递给另外一个 auto_ptr,你就不再拥有这个对象了。**
上述拷贝构造与拷贝赋值分别如下面两张图所示:
针对这个问题,在C++11标准出来之前,C++98标准中都一直只有一个智能指针auto_ptr,我们知道,这是一个失败的设计。它的本质是**管理权的转移**,这有许多问题。而这时就有一群人开始扩展C++标准库的关于智能指针的部分,他们组成了boost社区,他们负责boost库的开发和维护。其目的是为C++程序员提供免费的、同行审查的、可移植的程序库。boost库可以和C++标准库完美的共同工作,并且为其提供扩展功能。现在的**C++11标准库**的智能指针很大程度上“借鉴”了boost库。
boost::scoped_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件`#include<boost/smart_ptr.hpp> `可以使用。scoped_ptr 跟 auto_ptr 一样,可以方便的管理单个堆内存对象,特别的是,scoped_ptr 独享所有权,避免了auto_ptr恼人的几个问题。
<u>scoped_ptr是一种简单粗暴的设计,它本质就是**防拷贝**,避免出现管理权的转移。</u>这是它的最大特点,所以他的拷贝构造函数和赋值运算符重载函数都只是声明而不定义,而且为了防止有的人在类外定义,所以将函数声明为private。但这也是它最大的问题所在,就是不能赋值拷贝,也就是说功能不全。但是这种设计比较高效、简洁。没有 release() 函数,不会导致先前的内存泄露问题。下面我也将模拟实现scoped_ptr的管理机制(实际上就是前面提到的禁止拷贝):
```cpp
template<class T>
class scoped_ptr // noncopyable
{
public:
explicit scoped_ptr(T *ptr = 0) noexcept : ptr_(ptr) {
}
~scoped_ptr() noexcept {
delete ptr_;
}
void reset(T *p = 0) noexcept {
scoped_ptr(p).swap(*this);
}
T &operator*() const noexcept {
return *ptr_;
}
T *operator->() const noexcept {
return ptr_;
}
T *get() const noexcept {
return ptr_;
}
void swap(scoped_ptr &rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_);
}
private:
T *ptr_;
scoped_ptr(scoped_ptr const &);
scoped_ptr &operator=(scoped_ptr const &);
};
template<typename T>
void swap(scoped_ptr<T> &lhs, scoped_ptr<T> &rhs) noexcept {
lhs.swap(rhs);
}
```
scoped_ptr特