c++ 内存管理系统之智能指针

发布于:2025-03-04 ⋅ 阅读:(12) ⋅ 点赞:(0)

1.c++内存管理

1.代码区

也称Text Segment,存放可执行程序的机器码。

2 数据区:

存放已初始化的全局和静态变量, 常量数据(如字符串常量)。

存放未初始化的全局和静态变量

无疑解释静态变量的来源:

局部静态变量: 

存储在静态存储区,函数调用结束后不会销毁,下次调用函数时会保留上一次的值。

3.栈:

从高地址向低地址增长。由编译器自动管理分配。程序中的局部变量、函数参数值、返回变量等存在此区域。

4.堆

从低地址向高地址增长。容量大于栈,程序中动态分配的内存在此区域

2.new和delete问世

2.1对比 malloc new的优势

  • 自动调用构造函数和析构函数new 在分配内存后会自动调用对象的构造函数进行初始化,delete 在释放内存前会自动调用对象的析构函数进行清理工作,这对于管理复杂对象(如包含动态分配资源的对象)非常方便,能确保资源的正确初始化和释放。

这个无疑是new 能复制对象

  • 类型安全new 会根据对象的类型自动计算所需的内存大小,返回的指针类型也是正确的,不需要进行强制类型转换,减少了因类型转换错误导致的潜在问题。

malloc返回的是 void* 它得强转

  • 操作符重载:在 C++ 中,new 和 delete 是操作符,可以被重载,允许用户自定义内存分配和释放的行为,以满足特定的需求。

操作方便

2.2代码实现

#include <iostream>
using namespace std;
class A {
public:
    A() {
        std::cout << "A 的构造函数被调用" << std::endl;
    }
    ~A() {
        std::cout << "A 的析构函数被调用" << std::endl;
    }
};

int main()
{
	
	// 单个变量
	int *p= new int;
	*p=32;
	std::cout << *p << std::endl;
	
	delete p;

	//数组
	int a[4]={1,23,3,31};//不能赋值
	
	int *ptr=new int[4]{1,2,3};//
	
	cout<<ptr[1]<<endl;
	
	delete [] ptr;
	
	//类
	
	A*ptr_class=new A;
	
	delete ptr_class;
	
	//类数组
	A* ptr_classArray=new A[3];
	
	delete [] ptr_classArray;

	
	
}

基本格式为 变量类型* ptr = new 变量类型 (数组加[size])

可以重载:

#include <iostream>
#include <cstdlib>

class MyClass {
public:
    // 重载全局 new 操作符
    static void* operator new(size_t size) {
        std::cout << "自定义 new 操作符被调用,分配 " << size << " 字节" << std::endl;
        return std::malloc(size);
    }
    // 重载全局 delete 操作符
    static void operator delete(void* ptr) {
        std::cout << "自定义 delete 操作符被调用" << std::endl;
        std::free(ptr);
    }
    MyClass() { std::cout << "MyClass 构造函数被调用" << std::endl; }
    ~MyClass() { std::cout << "MyClass 析构函数被调用" << std::endl; }
};

int main() {
    MyClass* obj = new MyClass;
    delete obj;
    return 0;
}

3.智能指针问世

原因 因为你new 和 delete 是搭配使用的,等你返回,忘记删去怎么办!!!

#include <iostream>

int main()
{
	int *ptr=new int[4]{1,2,3,4};
	for(int i =0;i<4;i++)
	{
		if(i=2)
		{
			return ptr[i];
		}
	}
	delete []ptr;
	return	1;
}

3.1 unique_ptr

  • 特性

    • 独占所有权std::unique_ptr 对其所指向的资源拥有独占所有权,同一时间只能有一个 std::unique_ptr 指向该资源。这保证了资源管理的清晰性,避免多个指针同时操作同一资源导致的混乱。

    • 自动释放:当 std::unique_ptr 对象超出其作用域时,其析构函数会自动调用,从而释放其所指向的资源。

代码理解:

#include <iostream>
#include <memory>
using namespace std;

class MyClass {
public:
    MyClass() { std::cout << "MyClass 构造函数" << std::endl; }
    ~MyClass() { std::cout << "MyClass 析构函数" << std::endl; }
};

int main() {
   
   	
   {
   	
   // init 第一个利用make_unique<> ()
        std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
        // 当 ptr 离开此作用域时,会自动释放资源
   }
   {
   	unique_ptr<MyClass>ptr=make_unique<MyClass>();
   	//不能赋值unique_ptr<MyClass>ptr1=ptr
   	
   	unique_ptr<MyClass>ptr1=std::move(ptr);//能右赋值
   }
 
   
    std::cout << "ptr 已释放资源" << std::endl;
    return 0;
}

3.2 shared_ptr

特性

    • 共享所有权std::shared_ptr 可以被多个 std::shared_ptr 对象共享同一个资源。它使用引用计数机制来跟踪有多少个 std::shared_ptr 指向同一资源。

    • 自动释放:当引用计数变为 0 时,即没有任何 std::shared_ptr 指向该资源时,资源会被自动释放(还有离开作用域自身减一  一般一对大括号)

#include <iostream>
#include <memory>
using namespace std;

class MyClass {
public:
    MyClass() { std::cout << "MyClass 构造函数" << std::endl; }
    ~MyClass() { std::cout << "MyClass 析构函数" << std::endl; }
};

int main() {
   
   	
/*
 {
   shared_ptr<MyClass>ptr1=make_shared<MyClass>();
 } //error: ‘ptr1’ was not declared in this scope  
*/ 

shared_ptr<MyClass>ptr1=make_shared<MyClass>();
   cout<<ptr1.use_count()<<endl;

shared_ptr<MyClass>ptr2=ptr1;//这个拷贝

cout<<ptr2.use_count()<<endl;

shared_ptr<MyClass>ptr3(new MyClass);//这个全新的

cout<<ptr3.use_count()<<endl;

}
/*
MyClass 构造函数
1
2
MyClass 构造函数
1
MyClass 析构函数
MyClass 析构函数
*/ 结果

3.3 weak_ptr

特性

  • 弱引用std::weak_ptr 是一种不控制资源生命周期的智能指针,它对 std::shared_ptr 管理的资源进行弱引用,不增加引用计数。

  • 防止循环引用:主要用于解决 std::shared_ptr 可能出现的循环引用问题,避免资源无法正常释放。

错误用法:

#include <iostream>
#include <string>
#include <memory>

using namespace std;



class Boy {
public:
	Boy() {
		cout << "Boy 构造函数" << endl;
	}

	~Boy() {
		cout << "~Boy 析构函数" << endl;
	}

	void setGirlFriend(shared_ptr<Girl> _girlFriend) {
		this->girlFriend = _girlFriend;
	}

private:
	shared_ptr<Girl> girlFriend;
};

class Girl {
public:
	Girl() {
		cout << "Girl 构造函数" << endl;
	}

	~Girl() {
		cout << "~Girl 析构函数" << endl;
	}

	void setBoyFriend(shared_ptr<Boy> _boyFriend) {
		this->boyFriend = _boyFriend;
	}

private:
	shared_ptr<Boy> boyFriend;
};


void useTrap() {
	shared_ptr<Boy> spBoy(new Boy());
	shared_ptr<Girl> spGirl(new Girl());

	// 陷阱用法
	spBoy->setGirlFriend(spGirl);
	spGirl->setBoyFriend(spBoy);
	// 此时boy和girl的引用计数都是2
}


int main(void) {
	useTrap();

	system("pause");
	return 0;
}

如下图:
当我们执行useTrap函数时,注意,是没有结束此函数,boy和girl指针其实是被两个智能指针托管的,所以他们的引用计数是2

 解析:

  • 对象创建
    • shared_ptr<Boy> spBoy(new Boy()); 创建了一个 Boy 对象,并使用 spBoy 来管理它,此时 Boy 对象的引用计数为 1。
    • shared_ptr<Girl> spGirl(new Girl()); 创建了一个 Girl 对象,并使用 spGirl 来管理它,此时 Girl 对象的引用计数为 1。
  • 相互引用
    • spBoy->setGirlFriend(spGirl); 使得 spBoy 内部的 girlFriend 成员(std::shared_ptr<Girl> 类型)指向 spGirl 所管理的 Girl 对象,Girl 对象的引用计数变为 2。
    • spGirl->setBoyFriend(spBoy); 使得 spGirl 内部的 boyFriend 成员(std::shared_ptr<Boy> 类型)指向 spBoy 所管理的 Boy 对象,Boy 对象的引用计数变为 2。
2. 函数作用域结束时的情况

当 useTrap 函数执行结束,spBoy 和 spGirl 离开作用域,它们会被销毁。按照引用计数的规则,这会使得 Boy 和 Girl 对象的引用计数各自减 1。

 

然而,由于循环引用的存在:

 
  • Boy 对象仍然被 spGirl 中的 boyFriend 成员引用,所以 Boy 对象的引用计数从 2 减为 1。
  • Girl 对象仍然被 spBoy 中的 girlFriend 成员引用,所以 Girl 对象的引用计数从 2 减为 1。
 

因为引用计数没有变为 0,std::shared_ptr 不会释放 Boy 和 Girl 对象所占用的内存,从而造成了内存泄漏。

 感觉girlfrend和boyfriend反了 才更好理解

真正的:

#include <iostream>
#include <memory>

using namespace std;

class Boy;

class Girl {
public:
    Girl() {
        cout << "Girl 构造函数" << endl;
    }
    ~Girl() {
        cout << "~Girl 析构函数" << endl;
    }
    void setBoyFriend(shared_ptr<Boy> _boyFriend) {
        this->boyFriend = _boyFriend;
    }
private:
    weak_ptr<Boy> boyFriend;
};

class Boy {
public:
    Boy() {
        cout << "Boy 构造函数" << endl;
    }
    ~Boy() {
        cout << "~Boy 析构函数" << endl;
    }
    void setGirlFriend(shared_ptr<Girl> _girlFriend) {
        this->girlFriend = _girlFriend;
    }
private:
    weak_ptr<Girl> girlFriend;
};

void useTrap() {
    shared_ptr<Boy> spBoy(new Boy());
    shared_ptr<Girl> spGirl(new Girl());

    spBoy->setGirlFriend(spGirl);
    spGirl->setBoyFriend(spBoy);
}

int main() {
    useTrap();
    return 0;
}

 解析:

使用 std::weak_ptr 作为类的成员变量,主要是为了打破 std::shared_ptr 之间可能出现的循环引用问题。当 Boy 对象的 girlFriend 成员使用 std::weak_ptr 时,它对 Girl 对象的引用不会增加 Girl 对象的引用计数,从而避免了循环引用导致的内存泄漏问题。

综上所述,虽然 std::weak_ptr 和 std::shared_ptr 是不同的智能指针类型,但 std::weak_ptr 提供了相应的构造和赋值机制,允许使用 std::shared_ptr 来初始化或赋值,这在解决循环引用问题时非常有用。

其他用法:

#include <iostream>
#include <memory>

// 打印 weak_ptr 相关信息的函数
void printWeakPtrInfo(const std::weak_ptr<int>& weak) {
    std::cout << "当前 weak_ptr 的引用计数为: " << weak.use_count() << std::endl;
    if (weak.expired()) {
        std::cout << "weak_ptr 所指向的对象已被销毁。" << std::endl;
    } else {
        // 使用 lock 函数获取 shared_ptr
        std::shared_ptr<int> shared = weak.lock();
        if (shared) {
            std::cout << "Value: " << *shared << std::endl;
        }
    }
}

int main() {
    // 创建一个 shared_ptr 并初始化为 42
    std::shared_ptr<int> shared = std::make_shared<int>(42);
    // 创建一个 weak_ptr 并关联到 shared_ptr
    std::weak_ptr<int> weak = shared;

    std::cout << "第一次调用 printWeakPtrInfo 函数:" << std::endl;
    printWeakPtrInfo(weak);

    // 释放 shared_ptr 管理的对象
    shared.reset();

    std::cout << "\n第二次调用 printWeakPtrInfo 函数:" << std::endl;
    printWeakPtrInfo(weak);

    return 0;
}