C++笔记---智能指针

news2024/11/14 11:04:17

1. 什么是智能指针

1.1 RALL设计思想

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种资源管理类的设计思想,广泛应用于C++等支持对象导向编程的语言中。它的核心思想是将资源的管理与对象的生命周期紧密绑定,通过在对象的构造函数中获取资源,并在析构函数中释放资源,以此来确保资源总是被正确管理,避免资源泄露等问题。RAII利用了C++中对象自动调用析构函数的特性,即使在发生异常的情况下,也能保证资源被适时释放

1.2 智能指针的概念

智能指针是C++标准库中的一种高级指针管理工具,它们通过自动管理动态分配的内存来帮助预防内存泄漏和其他资源管理错误。

简单来说,智能指针是包装了指针的类,当智能指针对象的生命周期结束时,其析构函数会对指针指向的动态资源进行释放。

也就是说,我们将动态申请的内存交给智能指针对象来管理,将其的生命周期与智能指针的生命周期进行绑定,以此实现对动态分配的内存的自动释放。

智能指针相当于是给普通的指针加上了一个自动释放资源的功能,在行为上模拟普通指针的行为,重载了各种指针相关的操作符(operator*、operator->、operator[]等),因此叫做智能指针。

1.3 智能指针的应用场景

在C++中,智能指针的引入是为了简化和自动化内存管理,特别是在处理动态分配内存时,智能指针能够帮助防止常见的内存错误,如内存泄漏、悬挂指针和双重删除等。

在C++11之前,程序员需要手动管理动态分配的内存,这要求他们在适当的时机使用new和delete操作符来分配和释放内存。

这种管理方式不仅容易出错,而且在遇到异常时尤其难以保证资源被正确清理。

下面程序中我们可以看到,假如异常被抛出,就会导致array1和array2未被正常释放。

我们要处理这种情况,可以考虑先将异常捕获并将资源释放之后重新抛出,但这样的做法可维护性差,每此申请动态资源都需要再catch块中新增释放操作。除此之外,假如程序的其他部分抛出了异常(例如new也可能抛出异常),catch块中的释放操作也不会被执行。

double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	} 
	else
	{
		return (double)a / (double)b;
	}
}

void Func()
{
	int* array1 = new int[10];
	int* array2 = new int[10];
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	} 
	catch(...)
	{
		cout << "delete []" << array1 << endl;
		cout << "delete []" << array2 << endl;
		delete[] array1;
		delete[] array2;
		throw; // 异常重新抛出,捕获到什么抛出什么
	} 
	// ...
	cout << "delete []" << array1 << endl;
	delete[] array1;
	cout << "delete []" << array2 << endl;
	delete[] array2;
} 

int main()
{
	try
	{
		Func();
	} 
	catch(const char* errmsg)
	{
		cout << errmsg << endl;
	} 
	catch(const exception & e)
	{
		cout << e.what() << endl;
	} 
	catch(...)
	{
		cout << "未知异常" << endl;
	} 
	return 0;
}

当引入智能指针之后,操作起来就要简单得多了:

template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		: _ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete[] " << _ptr << endl;
		delete[] _ptr;
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	T& operator[](size_t i)
	{
		return _ptr[i];
	}
private:
	T* _ptr;
};

double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	} 
	else
	{
		return (double)a / (double)b;
	}
}

void Func()
{
	SmartPtr<int> array1(new int[10]);
	SmartPtr<int> array2(new int[10]);
	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}

int main()
{
	try
	{
		Func();
	} 
	catch(const char* errmsg)
	{
		cout << errmsg << endl;
	} 
	catch(const exception & e)
	{
		cout << e.what() << endl;
	} 
	catch(...)
	{
		cout << "未知异常" << endl;
	} 
	return 0;
}


2. C++标准库中的智能指针

智能指针在设计时存在一个较大的难题:那就是拷贝时的行为如何设计?

假如我们直接将一个智能指针内部的指针拷贝给另一个智能指针,那么当两个智能指针的生命周期都结束时,同一个动态资源就会被释放两次。

针对这个问题,C++标准库中给出了几种不同的方案,这些智能指针都在<memory>这个头文件下面,我们包含<memory>就可以使用了。

其中weak_ptr比较特殊,它仅用于辅助shared_ptr处理循环引用的问题,我们在第3部分再解释。

2.1 std::auto_ptr

auto_ptr是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象,这是一个非常糟糕的设计,因为他会到被拷贝对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使用auto_ptr

C++11出来之前很多公司也是明令禁止使用这个智能指针的。

个人感觉auto_ptr是最贴合设计初衷的名字,应该给下面行为最接近普通指针shared_ptr使用,但可惜好名字被狗用了。

实现示例:

namespace lbz
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			: _ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
		}

		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 检测是否为⾃⼰给自己赋值
			if (this != &ap)
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return*this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		} 
		
		// 像指针一样使用
		T & operator*()
		{
			return *_ptr;
		} 
		
		T* operator->()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};
}

2.2 std::unique_ptr

std::unique_ptr是一种独占所有权的智能指针,它确保在任何时刻只有一个unique_ptr实例可以拥有和管理所指向的对象。一旦unique_ptr实例超出其作用域或被显式重置,它所管理的对象将被自动释放。

unique_ptr不支持拷贝操作,但可以通过移动语义来转让所有权。

实现示例
namespace lbz
{
	template<class T>
	class unique_ptr
	{
	public:
		explicit unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		// 像指针⼀样使用
		T & operator*()
		{
			return *_ptr;
		} 
		
		T* operator->()
		{
			return _ptr;
		} 
		
		// 禁止直接拷贝
		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

		unique_ptr(unique_ptr<T>&& sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		} 
		
		unique_ptr<T>& operator=(unique_ptr<T>&& sp)
		{
			delete _ptr;
			_ptr = sp._ptr;
			sp._ptr = nullptr;
		}

	private:
		T* _ptr;
	};
}

2.3 std::shared_ptr

std::shared_ptr提供了共享所有权的智能指针,允许多个shared_ptr实例共享同一个对象的所有权。对象的生命周期由最后一个持有该对象的shared_ptr实例决定。当没有任何shared_ptr实例指向该对象时,对象会被自动释放。

shared_ptr是行为最贴近普通指针的智能指针,实践当中的使用最多,所以一般提到智能指针默认就是指shared_ptr。

shared_ptr的原理

shared_ptr内部使用引用计数来跟踪当前有多少个shared_ptr实例正在引用同一个对象。

为了实现这一点,我们引入了一个动态申请的int对象来进行引用计数(即下面代码中的_pcount)。

当直接使用指针进行构造时,申请一个int对象为其计数;当进行拷贝构造时,int对象++。

注意,这里不能使用静态成员变量来进行计数,因为静态成员变量统计的是shared_ptr的数量,而不是指向某一个资源的shared_ptr的数量。

删除器

智能指针析构时默认是进行delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。

智能指针支持在构造时给一个删除器,所谓删除器本质就是一个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调用删除器去释放资源。

注意:unique_ptr的删除器通过在实例化模板时显式给出类型进行传递,shared_ptr只需要在构造函数的参数列表中给出可调用对象即可(lambda表达式使用更加方便)。

因为new[]经常使用,所以为了使用方便,unique_ptr和shared_ptr都特化了一份[]的版本:

unique_ptr<Date[]> up1(new Date[5]); 
shared_ptr<Date[]> sp1(new Date[5]); 

在释放资源时,会用delete[]来进行释放。 

其他注意事项

1. shared_ptr 除了支持用指向资源的指针构造,还支持 make_shared 用初始化资源对象的值直接构造:

template <class T, class... Args> 
shared_ptr<T> make_shared(Args&&... args);

 2. shared_ptr 和 unique_ptr 都支持了operator bool的类型转换,如果智能指针对象是一个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

3. shared_ptr 和 unique_ptr 的构造函数都使用了explicit 修饰,以防止普通指针隐式类型转换成智能指针对象。

实现示例

需要注意的是我们这里实现的shared_ptr和weak_ptr都是以最简洁的方式实现的,只能满足基本的功能,这里的weak_ptr.lock等功能是无法实现的,想要实现就要把shared_ptr和weak_ptr一起改了,把引用计数拿出来放到一个单独类型,shared_ptr和weak_ptr都要存储指向这个类的对象才能实现,有兴趣可以去翻翻源代码。

namespace lbz
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr, function<void(T*)> del = ([](T* ptr) {delete ptr; }))
			:_ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _del(sp._del)
		{
			(*_pcount)++;
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				delete _pcount;
				_del(_ptr);
				_pcount = nullptr;
				_ptr = nullptr;
			}
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			// 只剩一个,且自己给自己赋值时必须这样解决
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_del = sp._del;
				++(*_pcount);
			}

			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T& operator[](size_t i)
		{
			return _ptr[i];
		}

		T* get() const
		{
			return _ptr;
		}

		int use_count() const
		{
			return *_pcount;
		}

	private:
		T* _ptr;
		int* _pcount;
		function<void(T*)> _del;
	};
}

3. 循环引用

假如我们在双向链表中使用shared_ptr来管理资源会发生什么?

#include<memory>
using namespace std;

struct ListNode
{
	ListNode(int val)
		:_val(val)
	{}

	int _val;
	std::shared_ptr<ListNode> _prev = nullptr;
	std::shared_ptr<ListNode> _next = nullptr;
};

int main()
{
	std::shared_ptr<ListNode> n1(new ListNode(10));
	std::shared_ptr<ListNode> n2(new ListNode(20));
	
	n1->_next = n2;
	n2->_prev = n1;
	return 0;
}

n1结点不仅被n1所指向,也被n2结点的prev所指向,引用计数为2,n2同理。

当程序结束时,n1、n2的生命周期结束,对应结点的引用计数各自减一,但此时两个结点仍各有1的引用计数,不会被正常释放。

只有当n2被释放,n2->prev的生命周期结束,n1才会被释放;但n2要被释放就需要n1先被释放,使n1->next的生命周期结束。

这样就导致两个结点互相引用、相互依存,始终不会被释放。

为了解决这个问题,就引入了weak_ptr来辅助shared_ptr的使用,weak_ptr相比于shared_ptr,其不会增加被指向资源的引用计数,也不负责资源的释放,将prev和next替换为weak_ptr就可以避免循环引用。

std::weak_ptr

std::weak_ptr是一种辅助智能指针,它与shared_ptr一起使用,用于避免循环引用问题。

相比于shared_ptr,weak_ptr不增加对象的引用计数,因此它不会延长对象的生命周期。

weak_ptr不支持RAII,也不支持访问资源,所以我们看文档发现weak_ptr构造时不支持绑定到资源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引用计数,那么就可以解决上述的循环引用问题。

weak_ptr也没有重载operator* 和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr支持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引用计数,weak_ptr想访问资源时,可以调用lock返回一个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是一个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

namespace lbz
{
    template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}
	
	private:
		T * _ptr = nullptr;
	};
} 

4. 智能指针使用建议

  • 优先使用unique_ptr来管理单个对象的所有权,因为它通常比shared_ptr更轻量级且更不容易产生错误。
  • 使用shared_ptr来管理共享所有权的资源,尤其是在多个对象或函数需要访问同一个资源的情况下。
  • 使用weak_ptr来避免与shared_ptr相关的循环引用问题。
  • 避免在全局作用域或静态存储持续期间使用智能指针,因为这可能会导致不必要的复杂性和潜在的资源管理问题。
  • 在可能发生异常的代码路径中使用智能指针,以确保即使在异常发生时资源也能被正确释放。
  • 当向容器或算法传递所有权时,使用std::move来将unique_ptr转换为右值引用,以便容器或算法可以接管所有权。

5. shared_ptr的线程安全问题

shared_ptr的引用计数对象在堆上,如果多个shared_ptr对象在多个线程中,进行shared_ptr的拷贝析构时会访问修改引用计数,就会存在线程安全问题,所以shared_ptr引用计数是需要加锁或者原子操作保证线程安全的。

shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr管,它也管不了,应该有外层使用shared_ptr的人进行线程安全的控制。

下面的程序会崩溃或者A资源没释放,bit::shared_ptr引用计数从int*改成atomic<int>*就可以保证引用计数的线程安全问题,或者使用互斥锁加锁也可以。

struct AA
{
	int _a1 = 0;
	int _a2 = 0;
	~AA()
	{
		cout << "~AA()" << endl;
	}
};

int main()
{
	bit::shared_ptr<AA> p(new AA);
	const size_t n = 100000;
	mutex mtx;
	auto func = [&]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				// 这里智能指针拷贝会++计数
				bit::shared_ptr<AA> copy(p);
				{
					unique_lock<mutex> lk(mtx);
					copy->_a1++;
					copy->_a2++;
				}
			}
		};
	thread t1(func);
	thread t2(func);
	t1.join();
	t2.join();
	cout << p->_a1 << endl;
	cout << p->_a2 << endl;
	cout << p.use_count() << endl;
	return 0;
}

6. C++11和boost中智能指针的关系

Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,Boost社区建立的初衷之一就是为C++的标准化工作提供可供参考的实现,Boost社区的发起人Dawes本人就是C++标准委员会的成员之一。

在Boost库的开发中,Boost社区也在这个方向上取得了丰硕的成果,C++11及之后的新语法和库有很多都是从Boost中来的。

• C++ 98 中产生了第一个智能指针auto_ptr;
• C++ boost给出了更实用的scoped_ptr / scoped_array和shared_ptr / shared_array和

  weak_ptr等;
• C++ TR1,引入了shared_ptr等,不过注意的是TR1并不是标准版;
• C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。

需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

7. 内存泄露

7.1 内存泄露的定义和危害

内存泄露是指程序在运行过程中动态分配的内存未能被正确释放或回收,导致这部分内存无法供其他程序或同一程序的其他部分使用。随着时间的推移,未被释放的内存会被不断累积,可能导致系统可用内存减少,进而使程序运行变慢、响应时间增加,甚至可能引发系统崩溃或资源耗尽。

7.2 如何检测内存泄露

检测内存泄露通常需要使用专门的工具,这些工具可以在程序运行时分析内存的分配和释放情况。常见的内存泄露检测工具包括Valgrind、AddressSanitizer、Visual Studio的内存诊断工具等。这些工具能够帮助开发人员识别不被释放的内存分配,并定位到代码中的具体位置。

7.3 如何避免内存泄露

避免内存泄露的关键在于遵循良好的编程习惯和系统设计原则:

  1. 严格管理内存分配和释放,确保每次动态内存分配后都有相应的释放操作。
  2. 使用智能指针和内存池等技术来自动管理内存,减少手动内存管理的错误。
  3. 进行定期的代码审查,以发现潜在的内存泄露风险点。
  4. 编写单元测试,确保内存管理的正确性。
  5. 对于不熟悉的库或框架,仔细阅读文档,了解其内存管理的细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2239046.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「QT」几何数据类 之 QVector4D 四维向量类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…

docker desktop运行rabittmq容器,控制台无法访问

docker desktop运行rabittmq容器&#xff0c;控制台无法访问 启动过程&#xff1a;…此处缺略&#xff0c;网上一大堆 原因 原因是在Docker上运行的RabbitMQ&#xff0c;默认情况下是没有启用管理插件和管理页面的 解决办法 使用命令 docker exec -it 容器id /bin/bash 进…

MySQL-事务(详细版)

目录 事务的含义 举一个例子 事务的特征&#xff08;面试高频&#xff09; 原子性 一致性 隔离性 持久性 事务结束 查看事务提交方式 查看事务提交的变量值&#xff1a;on 自动提交 off 不是自动提交 实例 事务回滚 验证事务回滚 事务实现&#xff1a;是数据库提…

Python “文件和IO操作” ——Python面试100道实战题目练习,巩固知识、检查技术、成功就业

本文主要是作为Python中“文件和IO操作”的一些题目&#xff0c;方便学习完Python的函数之后进行一些知识检验&#xff0c;感兴趣的小伙伴可以试一试&#xff0c;含选择题、判断题、填空题。 在做题之前可以先学习或者温习一下Python的异常处理机制&#xff0c;推荐阅读下面这篇…

【Docker】自定义网络:实现容器之间通过域名相互通讯

文章目录 一. 默认网络&#xff1a;docker0网络的问题二. 自定义网络三. nginx容器指之间通过主机名进行内部通讯四. redis集群容器&#xff08;跳过宿主机&#xff09;内部网络通讯1. 集群描述2. 基于bitnami镜像的环境变量快速构建redis集群 一. 默认网络&#xff1a;docker0…

Kafka-Eagle的配置——kafka可视化界面

通过百度网盘分享的文件&#xff1a;kafka-eagle-bin-2.0.8.tar.gz 链接&#xff1a;https://pan.baidu.com/s/1H3YONkL97uXbLTPMZHrfdg?pwdsltu 提取码&#xff1a;sltu 一、界面展示 二、软件配置 1、关闭kafka集群 kf.sh stop 2、将该软件上传到/opt/modules下 cd /opt…

计算机图形学论文 | 木工设计与制造计划的共同优化

&#x1f98c;&#x1f98c;&#x1f98c;读论文 我们的系统共同探索离散设计变量和制造计划的空间&#xff0c;以生成&#xff08;设计&#xff0c;制造计划&#xff09;对的帕累托前沿&#xff0c;使制造成本最小化。在该图中&#xff0c;(a)是椅子的输入设计和仅探索该设计的…

【百日算法计划】:每日一题,见证成长(023)

题目 每日温度 请根据每日气温列表,重新生成一个列表,对应的位置的输出为:要想观测到更高的气温,至少需要等待的天数.如果气温在这之后都不再升高,则该位置用0代替. 例如,给定一个列表[73, 74, 65, 80,] 你的输出应该是[1, 2, 1, 0] 思路 首先&#xff0c;我们先来画图分析一…

【在Linux世界中追寻伟大的One Piece】多路转接epoll

目录 1 -> I/O多路转接之poll 1.1 -> poll函数接口 1.2 -> poll的优点 1.3 -> poll的缺点 1.4 -> poll示例 1.4.1 -> 使用poll监控标准输入 2 -> I/O多路转接之epoll 2.1 -> 初识epoll 2.2 -> epoll的相关系统调用 2.2.1 -> epoll_cre…

推动AI云产业向深向实,云·AI·算力创新发展大会即将启幕

近年来&#xff0c;以AIGC为代表的新兴技术正加速演进&#xff0c;全球站在智能化变革的起点&#xff0c;人工智能与云计算的深度融合&#xff0c;也驱动云计算进入第三次发展浪潮&#xff0c;迎来前所未有的机遇。 伴随AI的快速发展&#xff0c;2024年《政府工作报告》明确提…

5G 现网信令参数学习(3) - RrcSetup(1)

目录 1. rlc-BearerToAddModList 1.1 rlc-Config 1.1.1 ul-AM-RLC 1.1.2 dl-AM-RLC 1.2 mac-LogicalChannelConfig 2. mac-CellGroupConfig 2.1 schedulingRequestConfig 2.2 bsr-Config 2.3 tag-Config 2.4 phr-Config 2.5 skipUplinkTxDynamic 3. physicalCellG…

【MyBatis源码】深入分析TypeHandler原理和源码

文章目录 原始 JDBC 存在的问题自定义 TypeHandler 实现TypeHandler详解BaseTypeHandler类TypeReference类型参考器43个类型处理器类型注册表&#xff1a;3个注解类枚举类 原始 JDBC 存在的问题 MyBatis 之所以设计了 TypeHandler&#xff08;类型处理器&#xff09;&#xff…

运营篇|公众号小程序如何实现流量主变现

公众号小程序如何实现流量主变现&#xff1f;三步走&#xff1a; ①开通流量主&#xff1b; ②接入广告&#xff1b; ③裂变传播&#xff1b; 一、小程序开通流量主的运营要求 微信小程序开通流量主的前提条件主要包括以下几个方面&#xff1a; 日活跃用户&#xff08;DAU…

丹摩征文活动 | 轻松上手图像生成:FLUX.1遇上ComfyUI,让复杂变简单!

一、背景 FLUX.1是由黑森林实验室推出的一款前沿图像生成模型&#xff0c;针对不同用户需求&#xff0c;提供了三种不同版本。 FLUX.1-pro是专为企业设计的闭源版本&#xff0c;拥有无与伦比的性能和生成能力。其在识别提示词、提升视觉质量、细节表现及输出多样性方面都有出…

PyTorch核心概念:从梯度、计算图到连续性的全面解析(三)

文章目录 Contiguous vs Non-Contiguous TensorTensor and ViewStrides非连续数据结构&#xff1a;Transpose( )在 PyTorch 中检查Contiguous and Non-Contiguous将不连续张量&#xff08;或视图&#xff09;转换为连续张量view() 和 reshape() 之间的区别总结 参考文献 Contig…

「IDE」集成开发环境专栏目录大纲

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「IDE」集成开发环境&#x1f4da;全部专栏「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C」C/C程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定…

Kafka经典面试题

1、kafka消息发送的流程&#xff1f; producer发送过程中启动两个线程 一个main线程 一个sender线程&#xff0c;在main线程中先创建一个双端队列&#xff08;RecordAccumlator、producerbatch&#xff09;&#xff0c;main将我们需要发送的东西经过拦截器&#xff0c;序列化&a…

手把手写深度学习(29):将DDP训练代码改成DeepSpeed

手把手写深度学习(0)&#xff1a;专栏文章导航 前言&#xff1a;deepspeed已经成为了大模型时代训练模型的常规武器&#xff0c;这篇博客以一个基于DDP的 Stable Diffusion模型训练为例&#xff0c;讲解如何从将DDP训练代码改成DeepSpeed。 目录 构建optimizer 构建scheduler…

信息收集系列(二):ASN分析及域名收集

内容预览 ≧∀≦ゞ 信息收集系列&#xff08;二&#xff09;&#xff1a;ASN分析及域名收集前言一、ASN 分析1. 获取 ASN 码2. 使用 ASNMap 获取 IP 范围3. 将 IP 范围转化为 IP 列表 二、关联域名收集1. 顶级域&#xff08;TLD&#xff09;收集测试方法 2. 根域名收集常用方法…

《数学分析》中不等式及补充

说明&#xff1a;此文章用于本人复习巩固&#xff0c;如果也能帮到大家那就更加有意义了。 注&#xff1a;1&#xff09;《数学分析》中的不等式及不等式的补充