Unique_ptr
实现目标
- 基于排他所有权模式:两个指针不能指向同一个资源。
- 由于独占对象的拥有权,所以不提供拷贝构造函数和左值赋值函数重载。
- 提供移动构造和移动赋值函数。
- 为了实现单个对象和一组对象的管理,添加了删除器类型。
- 在容器保存指针是安全。
- unique_ptr具有->和*运算符重载符,因此它可以像普通指针一
实现细节
template<class _Ty, class _Dx = Default_deleter<_Ty>>
class My_unique_ptr
{
public:
/*explicit 指定构造函数或转换函数 (C++11 起)或推导指引 (C++17 起)为显式,即它不能用于隐式转换和复制初始化。
*/
explicit My_unique_ptr(_Ty* ptr = nullptr) :m_ptr(ptr) { std::cout << "creat My_unique_ptr"; }
~My_unique_ptr() { m_deleter(m_ptr); }
public:
using pointer = _Ty*;
using element_type = _Ty;
using deleter_type = _Dx;
pointer m_ptr;
_Dx m_deleter;
};
1. 显示构造函数和析构函数
/*explicit*/My_unique_ptr(_Ty* ptr = nullptr) :m_ptr(ptr)
{ std::cout << "creat My_unique_ptr";
}
~My_unique_ptr()
{
m_deleter(m_ptr);
}
class Int
{
public:
Int() :x_(0) { std::cout << "creat Int"; x_ += 1; }
Int(int x) :x_(x) { std::cout << "creat Int"; }
Int(int x, int y):x_(x) { std::cout << "creat Int"; }
void Print()
{
std::cout << "Int::" << x_;
}
public:
int x_;
};
2. 为了满足智能指针的唯一性,不允许拷贝构造和赋值运算符的重载
My_unique_ptr(const My_unique_ptr&) = delete;
My_unique_ptr& operator=(const My_unique_ptr&) = delete;
3. 提供移动构造和移动赋值
My_unique_ptr(My_unique_ptr&& other) :m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
My_unique_ptr& operator=(My_unique_ptr&& other)
{
if (this != &other)
{
delete m_ptr;
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
return *this;
} //xxxx
4. 实现删除器
template<class _Ty>
struct Default_deleter
{
void operator() (_Ty* ptr)
{
delete ptr;
ptr = nullptr;
std::cout << "destroyed";
}
};
template<class _Ty>
struct Default_deleter<_Ty[]>
{
void operator ()(_Ty* ptr)
{
delete[]ptr;
ptr = nullptr;
std::cout << "destroyed ";
}
};
Shared_ptr
实现目标
- 它可以在多个指针之间共享同一个对象,从而避免了内存泄漏和野指针的问题。
- shared_ptr 的实现方式通常是基于引用计数的技术,具体来说,每个 shared_ptr 对象都会维护一个引用计数器,用于记录有多少个指针指向同一个对象。当引用计数器为 0 时,表示没有任何指针指向该对象,此时会自动释放该对象的内存空间。
实现细节
#include <iostream>
#include <vector>
#include <unordered_set>
template <typename T>
class shared_ptr {
private:
T* ptr;
int* ref_count;
void release() {
if (ref_count) {
--(*ref_count);
if (*ref_count == 0) {
delete ptr;
delete ref_count;
}
ptr = nullptr;
ref_count = nullptr;
}
}
public:
shared_ptr() : ptr(nullptr), ref_count(nullptr) {}
shared_ptr(T* p) : ptr(p), ref_count(new int(1)) {}
shared_ptr(const shared_ptr& other) : ptr(other.ptr), ref_count(other.ref_count) {
if (ref_count) {
++(*ref_count);
}
}
~shared_ptr() {
release();
}
shared_ptr& operator=(const shared_ptr& other) {
if (this != &other) {
release();
ptr = other.ptr;
ref_count = other.ref_count;
if (ref_count) {
++(*ref_count);
}
}
return *this;
}
T* get() const {
return ptr;
}
int use_count() const {
return ref_count ? *ref_count : 0;
}
void reset() {
release();
}
void reset(T* p) {
release();
ptr = p;
ref_count = new int(1);
}
T& operator*() const {
return *ptr;
}
T* operator->() const {
return ptr;
}
};
class MyClass {
public:
MyClass() {
std::cout << "MyClass constructor" << std::endl;
}
~MyClass() {
std::cout << "MyClass destructor" << std::endl;
}
void hello() {
std::cout << "Hello, world!" << std::endl;
}
};
int main() {
shared_ptr<MyClass> ptr1(new MyClass());
shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
ptr1->hello();
ptr2.reset();
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
return 0;
}
1. 引用计数器
在构造函数中,我们将 ref_count 初始化为 1,表示有一个指针指向该对象。在拷贝构造函数和赋值运算符中,我们将 ref_count 的值加 1,表示有一个新的指针指向该对象。在析构函数中,我们将 ref_count 的值减 1,如果 ref_count 的值为 0,则表示没有任何指针指向该对象,此时会自动释放该对象的内存空间。
2. 其它细节
在使用 shared_ptr 时,我们可以通过 get 函数获取指向对象的指针,通过 use_count 函数获取当前有多少个指针指向该对象。同时,我们还可以通过 reset 函数释放当前指针,并重新指向一个新的对象。此外,我们还可以通过 operator* 和 operator-> 运算符重载,使得 shared_ptr 对象可以像普通指针一样使用。
3. 为什么用引用计数而不用static变量呢?
static 变量可以被多个变量共享,但是这种共享方式是基于静态存储期的,而不是基于动态存储期的。具体来说,当我们定义一个 static 变量时,它会在程序运行期间一直存在,直到程序结束才会被销毁。在这个过程中,所有使用该 static 变量的变量都会共享同一个内存空间,从而实现了多变量共享的功能。
然而,在实现 shared_ptr 时,我们需要的是动态存储期的共享,即多个 shared_ptr 对象之间需要共享同一个对象的引用计数。这种共享方式是基于动态存储期的,因为 shared_ptr 对象是在程序运行期间动态创建和销毁的,而不是在程序编译期间就确定的。因此,使用 static 变量来记录引用计数无法实现动态存储期的共享,从而导致引用计数的不准确和内存泄漏的问题。
Comments NOTHING