将上一节的RAII代码模板化,
并且添加&、->、bool等运算符重载:
template <typename T>
class smart_ptr {
public:
explicit smart_ptr(T* ptr = nullptr) : ptr_(ptr) {}
~smart_ptr() { delete ptr_; }
T* get() { return ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
private:
T* ptr_;
};
目前的代码,在拷贝时会赋值对象指针,有内存释放两次的隐患,
我们可以禁用 拷贝构造和赋值,避免对象指针的复制:
template <typename T>
class smart_ptr {
public:
smart_ptr(const smart_ptr&) = delete;
smart_ptr& operator=(const smart_ptr&) = delete;
private:
T* ptr_;
};
也可以在复制时转移对象指针的所有权:
这里的赋值分为拷贝构造和swap两步
- 首先是smart_ptr(rhs),调用了拷贝构造,
生成了一个临时变量,拿出了rhs的指针所有权 - 然后与临时变量交换指针所有权
异常只可能出现在拷贝构造部分。
分成两步是为了防止在拷贝的过程中出现异常,导致当前对象收到影响。
template <typename T>
class smart_ptr {
public:
explicit smart_ptr(T* ptr = nullptr) : ptr_(ptr) {}
~smart_ptr() { delete ptr_; }
smart_ptr(smart_ptr& other) { ptr_ = other.release(); }
// 这里的参数可以直接把引用改成拷贝
// 这样的话函数内部就不需要自己创建临时变量了
smart_ptr& operator=(smart_ptr& rhs) {
smart_ptr(rhs).swap(*this);
return *this;
}
// 交出指针所有权
T* release() {
T* ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
// 交换指针
void swap(smart_ptr& rhs) { std::swap(ptr_, rhs.ptr_); }
T* get() { return ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
private:
T* ptr_;
};
上面的实现其实很不合理,因为没有显式的告诉你拷贝会转移指针所有权,
在使用时容易不小心把指针传走了。
可以在使用移动构造时,才转移指针所有权:
(cpp里如果提供了移动构造,那么拷贝构造是默认禁用的,不需要再显式禁用)
template <typename T>
class smart_ptr {
public:
explicit smart_ptr(T* ptr = nullptr) : ptr_(ptr) {}
~smart_ptr() { delete ptr_; }
smart_ptr(smart_ptr&& other) { ptr_ = other.release(); }
//赋值时,参数以拷贝的方式传入,不再需要手动创建临时变量
smart_ptr& operator=(smart_ptr rhs) {
rhs.swap(*this);
return *this
}
// 交出指针所有权
T* release() {
T* ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
// 交换指针
void swap(smart_ptr& rhs) { std::swap(ptr_, rhs.ptr_); }
T* get() { return ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
private:
T* ptr_;
};