1. 第一步,把wrapper改造为模板类

template <typename T>
class smart_ptr
{
public:
    explicit smart_ptr(T * ptr = nullptr)
        : ptr_(ptr){}
    ~smart_ptr() {delete ptr_;}
    T * get() const {return ptr_;}
private:
    T * ptr_;
}

2. 第二步:仿指针行为

T& operator*() const {return *ptr_;}
T* operator->() const {return ptr_;}
operator bool() const {return ptr_;}

3. 第三步:实现资源移动

3.1. 策略一:禁止拷贝

使用后,一个空间只会被一个smart_ptr指向。
实现方法:

smart_ptr(const smart_ptr &) = delete;
smart_ptr& operator=(const smart_ptr&) = delete;

3.2. 策略二:拷贝时自动移动资源

使用后,一个空间只会被一个smart_ptr指向。
可以拷贝。拷贝后,新smart_ptr指向空间,旧smart_ptr失效。

T* release()
{
    T* ptr = ptr_;
    ptr_ = nullptr;
    return ptr;
}

smart_ptr(smart_ptr & other)
{
    ptr_ = other.release();
}

void swap(smart &rhs)
{
    using std::swap;
    swap(ptr_, rhs.ptr_);
}

smart_ptr& operator=(smart_ptr &rhs)
{
    smart_ptr(rhs).swap(*this);
    return *this;
}

说明:用拷贝构造 + swap实现拷贝赋值是一种惯用法,用于保证异常安全

3.2.1. 策略二存在的问题

template <typename T>
void print_addr(smart_ptr<ptr>)
{
    ....
}

执行print_addr(ptr);之后ptr会变成空指针。
因为ptr的内容移动到了print_addr的栈上,而函数返回时print_addr的栈又被释放掉了。
策略二是auto_ptr的行为,它有风险,因此在C++ 19中被取消掉了。

3.3. 策略三:利用右值引用,区分拷贝和移动

smart_ptr(smart_ptr&& other)
{
    ptr_ = other.release();
}
smart_ptr& operator=(smart_ptr rhs)
{
    rhs.swap(*this);
    return *this;
}

说明:
smart_ptr(smart_ptr&& other)是一个移动构造函数
operator=的形参smart_ptr rh,根据实参选择调用移动构造还是拷贝构造。这样不需要分别实现移动赋值和拷贝赋值。
但此处没有实现拷贝构造,因此也不存在拷贝赋值。

3.3.1. 策略三的使用方法

  1. smart_ptr<T> ptr1{create()};
    {}内的是一个右值,因此调用移动构造。

  2. smart_ptr<T> ptr2{ptr1};
    ptr1是一个左值,因此调用拷贝构造函数。但没有实现拷贝构造函数,因此编译出错。

  3. smart_ptr<T> ptr3;
    ptr3 = ptr1;
    

    ptr1是一个左值,调用拷贝赋值,编译出错。

  4. ptr3 = std::move(ptr1)
    将左值ptr1转为右值,调用移动赋值。

  5. smart_ptr<T> ptr4{std::move(ptr3}; 左值转为右值,调用移动构造。

3.3.2. 说明

策略三是unique_ptr的行为。
策略三如果需要实现子类指针向父类指针的自动转换,需要再加一个函数

template <typename U>
smart_ptr(smart_ptr<U> &&ohter)
{
    ptr_ = other.release();
}

3.4. 策略四:共享和计数

多个指针指向同一内存,且指向同一个计数器。
第一个创建空间的指针同时创建和初始化计数器。
计数器减为0时释放空间。

3.4.1. 实现

策略四需要提供以下几种构造函数:

// 模板拷贝构造函数
template <typename U>
smart_ptr(const smart_ptr<U> & other)

// 模板移动构造
template<typename U>
smart_ptr(const smart_ptr<U> && other)

// 拷贝构造函数
smart_ptr(const smart_ptr& other)

说明:
如果无显式的拷贝构造函数,编译器会提供一个错误的。如果仅提供了模板拷贝构造函数,编译器不会认为是有显式的拷贝构造函数。
如果有显式的拷贝构造函数,编译器就不会提供移动构造函数,那么所有移动构造会调用上面的“模板移动构造”,不会出错。

3.5. 实现对象移动的总结

  1. 分别实现拷贝构造和移动构造,除非这个对象只能移动不能拷贝。如unique_ptr
    无拷贝只移动,参考策略二unique_ptr。策略一auto_pr是反而教材。
  2. 对象应有swap成员函数,用于与另一个对象快速交换成员。
    对象的名空间下有自由swap函数,调用成员函数的swap实现交换。
  3. 实现一份能用的operator=,可自动适配移动赋值和拷贝赋值。
  4. 移动函数和swap函数不能抛异常,noexcept

results matching ""

    No results matching ""