C++ 智能指针的原理、分类、使用
1. 智能指针介绍
为解决裸指针可能导致的内存泄漏问题。如:
a)忘记释放内存;
b)程序提前退出导致资源释放代码未执行到。
就出现了智能指针,能够做到资源的自动释放。
2. 智能指针的原理和简单实现
2.1 智能指针的原理
将裸指针封装为一个智能指针类,需要使用该裸指针时,就创建该类的对象;利用栈区对象出作用域会自动析构的特性,保证资源的自动释放。
2.2 智能指针的简单实现
代码示例:
template<typename T>
class MySmartPtr {
public:MySmartPtr(T* ptr = nullptr):mptr(ptr) { // 创建该对象时,裸指针会传给对象}~MySmartPtr() { // 对象出作用域会自动析构,因此会释放裸指针指向的资源delete mptr;}// *运算符重载T& operator*() { // 提供智能指针的解引用操作,即返回它包装的裸指针的解引用return *mptr; }// ->运算符重载T* operator->() { // 即返回裸指针return mptr;}
private:T* mptr;
};class Obj {
public:void func() {cout << "Obj::func" << endl;}
};void test01() {/*创建一个int型的裸指针,使用MySmartPtr将其封装为智能指针对象ptr,ptr对象除了作用域就会自动调用析构函数。智能指针就是利用栈上对象出作用域自动析构这一特性。*/MySmartPtr<int> ptr0(new int);*ptr0 = 10;MySmartPtr<Obj> ptr1(new Obj);ptr1->func();(ptr1.operator->())->func(); // 等价于上面/* 中间异常退出,智能指针也会自动释放资源。if (xxx) {throw "....";}if (yyy) {return -1;}*/
}
3. 智能指针分类
3.1 问题引入
接着使用上述自己实现的智能指针进行拷贝构造:
void test02() {MySmartPtr<int> p1(new int); // p1指向一块int型内存空间MySmartPtr<int> p2(p1); // p2指向p1指向的内存空间*p1 = 10; // 内存空间的值为10*p2 = 20; // 内存空间的值被改为20
}
但运行时出错:
原因在于p1和p2指向同一块int型堆区内存空间,p2析构将该int型空间释放,p1再析构时释放同一块内存,则出错。
那可否使用如下深拷贝解决该问题?
MySmartPtr(cosnt MySmartPtr<T>& src) {mptr = new T(*src.mptr);
}
不可以。因为按照裸指针的使用方式,用户本意是想将p1和p2都指向该int型堆区内存,使用指针p1、p2都可改变该内存空间的值,显然深拷贝不符合此场景。
3.2 两类智能指针
不带引用计数的智能指针:只能有一个指针管理资源。
auto_ptr;
scoped_ptr;
unique_ptr;.
带引用计数的智能指针:可以有多个指针同时管理资源。
shared_ptr;强智能指针。
weak_ptr: 弱智能指针。这是特例,不能控制资源的生命周期,不能控制资源的自动释放!
3.3 不带引用计数的智能指针
只能有一个指针管理资源。
3.3.1 auto_ptr (不推荐使用)
void test03() {auto_ptr<int> ptr1(new int);auto_ptr<int> ptr2(ptr1);*ptr2 = 20;// cout << *ptr2 << endl; // 可访问*ptr2cout << *ptr1 << endl; //访问*ptr1却报错
}
如上代码,访问*ptr1为何报错?
因为调用auto_ptr的拷贝构造将ptr1的值赋值给ptr2后,底层会将ptr1指向nullptr;即将同一个指针拷贝构造多次时,只让最后一次拷贝的指针管理资源,前面的指针全指向nullptr。
不推荐将auto_ptr存入容器。
3.3.2 scoped_ptr (使用较少)
scoped_ptr已将拷贝构造函数和赋值运算符重载delete了。
scoped_ptr(const scoped_ptr<T>&) = delete; // 删除拷贝构造
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete; // 删除赋值重载
3.3.3 unique_ptr (推荐使用)
unique_ptr也已将拷贝构造函数和赋值运算符重载delete。
unique_ptr(const unique_ptr<T>&) = delete; // 删除拷贝构造
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete; // 删除赋值重载
但unique_ptr提供了带右值引用参数的拷贝构造函数和赋值运算符重载,如下:
void test04() {unique_ptr<int> ptr1(new int);// unique_ptr<int> ptr2(ptr1); 和scoped_ptr一样无法通过编译unique_ptr<int> ptr2(std::move(ptr1)); // 但可使用move得到ptr1的右值类型// *ptr1 也无法访问
}
3.4 带引用计数的智能指针
可以有多个指针同时管理资源。
原理:给智能指针添加其指向资源的引用计数属性,若引用计数 > 0,则不会释放资源,若引用计数 = 0就释放资源。
具体来说:额外创建资源引用计数类,在智能指针类中加入该资源引用计数类的指针作为其中的一个属性;当使用裸指针创建智能指针对象时,创建智能指针中的资源引用计数对象,并将其中的引用计数属性初始化为1,当后面对该智能指针对象进行拷贝(使用其他智能指针指向该资源时)或时,需要在其他智能指针对象类中将被拷贝的智能指针对象中的资源引用计数类的指针获取过来,然后将引用计数+1;当用该智能指针给其他智能指针进行赋值时,因为其他智能指针被赋值后,它们就不指向原先的资源了,原先资源的引用计数就-1,直至引用计数为0时delete掉资源;当智能指针对象析构时,会使用其中的资源引用计数指针将共享的引用计数-1,直至引用计数为0时delete掉资源。
shared_ptr:强智能指针;可改变资源的引用计数。
weak_ptr:弱智能指针;不可改变资源的引用计数。
带引用计数的智能指针的简单实现:
/*资源的引用计数类*/
template<typename T>
class RefCnt {
public:RefCnt(T* ptr=nullptr):mptr(ptr) {if (mptr != nullptr) {mcount = 1; // 刚创建指针指针时,引用计数初始化为1}}void addRef() { // 增加引用计数mcount++;}int delRef() { // 减少引用计数mcount--;return mcount;}
private:T* mptr; // 资源地址int mcount; // 资源的引用计数
};/*智能指针类*/
template<typename T>
class MySmartPtr {
public:MySmartPtr(T* ptr = nullptr) :mptr(ptr) { // 创建该对象时,裸指针会传给对象mpRefCnt = new RefCnt<T>(mptr);}~MySmartPtr() { // 对象出作用域会自动析构,因此会释放裸指针指向的资源if (0 == mpRefCnt->delRef()) {delete mptr;mptr = nullptr;}}// *运算符重载T& operator*() { // 提供智能指针的解引用操作,即返回它包装的裸指针的解引用return *mptr;}// ->运算符重载T* operator->() { // 即返回裸指针return mptr;}// 拷贝构造MySmartPtr(const MySmartPtr<T>& src):mptr(src.mptr),mpRefCnt(src.mpRefCnt) {if (mptr != nullptr) {mpRefCnt->addRef();}}// 赋值重载MySmartPtr<T>& operator=(const MySmartPtr<T>& src) {if (this == &src) // 防止自赋值return *this;/*若本指针改为指向src管理的资源,则本指针原先指向的资源的引用计数-1,若原资源的引用计数为0,就释放资源*/if (0 == mpRefCnt->delRef()) { delete mptr;}mptr = src.mptr;mpRefCnt = src.mpRefCnt;mpRefCnt->addRef();return *this;}
private:T* mptr; // 指向资源的指针RefCnt<T>* mpRefCnt; // 资源的引用计数
};
3.4.1 shared_ptr
强智能指针。可改变资源的引用计数。
(1)强智能指针的交叉引用问题
class B;class A {
public:A() {cout << "A()" << endl;}~A() {cout << "~A()" << endl;}shared_ptr<B> _ptrb;
};class B {
public:B() {cout << "B()" << endl;}~B() {cout << "~B()" << endl;}shared_ptr<A> _ptra;
};void test06() {shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa->_ptrb = pb;pb->_ptra = pa;/*打印pa、pb指向资源的引用计数*/cout << pa.use_count() << endl;cout << pb.use_count() << endl;
}
输出结果:
可见pa、pb指向的资源的引用计数都为2,因此出了作用域导致pa、pb指向的资源都无法释放,如下图所示:
解决:
建议定义对象时使用强智能指针,引用对象时使用弱智能指针,防止出现交叉引用的问题。
什么是定义对象?什么是引用对象?
定义对象:
使用new创建对象,并创建一个新的智能指针管理它。
引用对象:
使用一个已存在的智能指针来创建一个新的智能指针。
定义对象和引用对象的示例如下:
shared_ptr<int> p1(new int()); // 定义智能指针对象p1
shared_ptr<int> p2 = make_shared<int>(10); // 定义智能指针对象p2shared_ptr<int> p3 = p1; // 引用智能指针p1,并使用p3来共享它
weak_ptr<int> p4 = p2; // 引用智能指针p2,并使用p4来观察它
如上述代码,因为在test06函数中使用pa对象的_ptrb引用pb对象,使用pb对象的_ptra引用pa对象,因此需要将A类、B类中的_ptrb和_ptra的类型改为弱智能指针weak_ptr即可,这样就不会改变资源的引用计数,能够正确释放资源。
3.4.2 weak_ptr
弱智能指针。不能改变资源的引用计数、不能管理对象生命周期、不能做到资源自动释放、不能创建对象,也不能访问资源(因为weak_ptr未提供operator->和operator*运算符重载),即不能通过弱智能指针调用函数、不能将其解引用。只能从一个已有的shared_ptr或weak_ptr获得资源的弱引用。
弱智能指针weak_ptr若想用访问资源,则需要使用lock方法将其提升为一个强智能指针,提升失败则返回nullptr。(提升的情形常使用于多线程环境,避免无效的访问,提升程序安全性)
注意:弱智能指针weak_ptr只能观察资源的状态,但不能管理资源的生命周期,不会改变资源的引用计数,不能控制资源的释放。
weak_ptr示例:
void test07() {shared_ptr<Boy> boy_sptr(new Boy());weak_ptr<Boy> boy_wptr(boy_sptr);// boy_wptr->study(); 错误!无法使用弱智能指针访问资源cout << boy_sptr.use_count() << endl; // 引用计数为1,因为弱智能指针不改变引用计数shared_ptr<int> i_sptr(new int(99));weak_ptr<int> i_wptr(i_sptr);// cout << *i_wptr << endl; 错误!无法使用弱智能指针访问资源cout << i_sptr.use_count() << endl; // 引用计数为1,因为弱智能指针不改变引用计数/*弱智能指针提升为强智能指针*/shared_ptr<Boy> boy_sptr1 = boy_wptr.lock();if (boy_sptr1 != nullptr) {cout << boy_sptr1.use_count() << endl; // 提升成功,引用计数为2boy_sptr1->study(); // 可以调用}shared_ptr<int> i_sptr1 = i_wptr.lock();if (i_sptr1 != nullptr) {cout << i_sptr1.use_count() << endl; // 提升成功,引用计数为2cout << *i_sptr1 << endl; // 可以输出}
}
4. 智能指针与多线程访问共享资源的安全问题
现要实现主线程创建子线程,让子线程执行打印Hello的函数,有如下两种方式:
方式1:主线程调用test08函数,在test08函数中启动子线程执行线程函数,如下:
void handler() {cout << "Hello" << endl;
}void func() {thread t1(handler);
}int main(int argc, char** argv) {func();this_thread::sleep_for(chrono::seconds(1));system("pause");return 0;
}
运行报错:
方式2:主线程中直接创建子线程来执行线程函数,如下:
void handler() {cout << "Hello" << endl;
}int main(int argc, char** argv) {thread t1(handler);this_thread::sleep_for(chrono::seconds(1));system("pause");return 0;
}
运行结果:无报错
上面两种方式都旨在通过子线程调用函数输出Hello,但为什么方式1报错?很简单,不再赘述。
回归本节标题的正题,有如下程序:
class C {
public:C() {cout << "C()" << endl;}~C() {cout << "~C()" << endl;}void funcC() {cout << "C::funcC()" << endl;}
private:};/*子线程执行函数*/
void threadHandler(C* c) {this_thread::sleep_for(chrono::seconds(1));c->funcC();
}/* 主线程 */
int main(int argc, char** argv) {C* c = new C();thread t1(threadHandler, c);delete c;t1.join();return 0;
}
运行结果:
结果显示c指向的对象被析构了,但是仍然使用该被析构的对象调用了其中的funcC函数,显然不合理。
因此在线程函数中,使用c指针访问A对象时,需要观察A对象是否存活。
使用弱智能指针weak_ptr接收对象,访问对象之前尝试提升为强智能指针shared_ptr,提升成功则访问,否则对象被析构。
情形1:对象被访问之前就被析构了:
class C {
public:C() {cout << "C()" << endl;}~C() {cout << "~C()" << endl;}void funcC() {cout << "C::funcC()" << endl;}
private:};/*子线程执行函数*/
void threadHandler(weak_ptr<C> pw) { // 引用时使用弱智能指针this_thread::sleep_for(chrono::seconds(1));shared_ptr<C> ps = pw.lock(); // 尝试提升if (ps != nullptr) {ps->funcC();} else {cout << "对象已经析构!" << endl;}
}/* 主线程 */
int main(int argc, char** argv) {{shared_ptr<C> p(new C());thread t1(threadHandler, weak_ptr<C>(p));t1.detach();}this_thread::sleep_for(chrono::seconds(5));return 0;
}
运行结果:
情形2: 对象访问完才被析构:
class C {
public:C() {cout << "C()" << endl;}~C() {cout << "~C()" << endl;}void funcC() {cout << "C::funcC()" << endl;}
private:};/*子线程执行函数*/
void threadHandler(weak_ptr<C> pw) { // 引用时使用弱智能指针this_thread::sleep_for(chrono::seconds(1));shared_ptr<C> ps = pw.lock(); // 尝试提升if (ps != nullptr) {ps->funcC();} else {cout << "对象已经析构!" << endl;}
}/* 主线程 */
int main(int argc, char** argv) {{shared_ptr<C> p(new C());thread t1(threadHandler, weak_ptr<C>(p));t1.detach();this_thread::sleep_for(chrono::seconds(5));} return 0;
}
运行结果:
可见shared_ptr与weak_ptr结合使用,能够较好地保证多线程访问共享资源的安全。
5.智能指针的删除器deleter
删除器是智能指针释放资源的方式,默认使用操作符delete来释放资源。
但并非所有智能指针管理的资源都可通过delete释放,如数组、文件资源、数据库连接资源等。
有如下智能指针对象管理一个数组资源:
unique_ptr<int> ptr1(new int[100]);
此时再用默认的删除器则会造成资源泄露,因此需要自定义删除器。
一些为部分自定义删除器的示例:
/* 方式1:类模板 */
template<typename T>
class MyDeleter {
public:void operator()(T* ptr) const {cout << "数组自定义删除器1." << endl;delete[] ptr;}
};/* 方式2:函数 */
void myDeleter(int* p) {cout << "数组自定义删除器2." << endl;delete[] p;
}void test09() {unique_ptr<int, MyDeleter<int>> ptr1(new int[100]);unique_ptr<int, void(*)(int*)> ptr2(new int[100], myDeleter);/* 方式3:Lambda表达式 */unique_ptr<int, void(*)(int*)> ptr3(new int[100], [](int* p) {cout << "数组自定义删除器3." << endl;delete[] p;});
}void test10() {unique_ptr<FILE, void(*)(FILE*)> ptr2(fopen("1.txt", "w"), [](FILE* f) {cout << "文件自定义删除器." << endl;fclose(f);});
}
运行结果:
待补充
相关文章:

C++ 智能指针的原理、分类、使用
1. 智能指针介绍 为解决裸指针可能导致的内存泄漏问题。如: a)忘记释放内存; b)程序提前退出导致资源释放代码未执行到。 就出现了智能指针,能够做到资源的自动释放。 2. 智能指针的原理和简单实现 2.1 智能指针的原…...

学习笔记——SVG.js中形状元素的创建及其相关方法
CreateElement 1)创建svg元素 在svg.js中,每个元素都是一个对象,可以通过构造它来创建: import { Rect } from "svgdotjs/svg.js" var rect new Rect().size(100, 100).addTo(draw) // or to reuse an existing nod…...

Linux一学就会——系统文件I/O
Linux一学就会——系统文件I/O 有几种输出信息到显示器的方式 #include <stdio.h> #include <string.h> int main() {const char *msg "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, &q…...

OpenCV-Python图像阈值
目录 简单阈值 自适应阈值 Otsu的二值化 所谓的图像阈值,就是图像二值化,什么是二值化,就是只有0和1,没有其他的。在OpenCV的图像里面,二值化表示图像的像素为0和255,并没有其他的值,它跟灰度…...

LangChain-Agents 入门指南
LangChain-Agents 入门指南 LangChain-Agents 入门指南注册 Serpapi运行高级 Agents API 测试运行 Google Search其它 Here’s the table of contents: LangChain-Agents 入门指南 LangChain是一个使用LLMs构建应用程序的工具箱,包含Models、Prompts、Indexes、Mem…...

深度学习-tensorflow 使用keras进行深度神经网络训练
概要 深度学习网络的训练可能会很慢、也可能无法收敛,本文介绍使用keras进行深度神经网络训练的加速技巧,包括解决梯度消失和爆炸问题的策略(参数初始化策略、激活函数策略、批量归一化、梯度裁剪)、重用预训练层方法、更快的优化…...

【NLP开发】Python实现聊天机器人(ChatterBot,集成前端页面)
🍺NLP开发系列相关文章编写如下🍺: 🎈【NLP开发】Python实现词云图🎈🎈【NLP开发】Python实现图片文字识别🎈🎈【NLP开发】Python实现中文、英文分词🎈🎈【N…...

Python 操作 Excel,如何又快又好?
➤数据处理是 Python 的一大应用场景,而 Excel 则是最流行的数据处理软件。因此用 Python 进行数据相关的工作时,难免要和 Excel 打交道。Python处理Excel 常用的系列库有:xlrd、xlwt、xlutils、openpyxl ◈xlrd - 用于读取 Exce…...

Spring Redis 启用TLS配置支持(踩坑解决)
由于线上Redis要启用TLS,搜遍了google百度也没一个标准的解决方案,要不这个方法没有,要不那个类找不到...要不就是配置了还是一直连不上redis.... 本文基于 spring-data-redis-2.1.9.RELEASE 版本来提供一个解决方案: 1.运维那边提供过来三个文件,分别是redis.crt redis.key …...

centOS7忘记登录密码该如何重新修改登录密码
文章目录 前言一、重新修改登录密码1.1、第一步1.2、第二步1.3、第三步1.4、第四步1.5、第五步1.6、第六步1.7、第七步1.8、第八步 前言 忘记密码并不可怕,只要学会方法,密码随时都可以找回。 一、重新修改登录密码 1.1、第一步 当打开centOS7之后忘记…...

揭开基于 AI 的推荐系统的神秘面纱:深入分析
人工智能 (AI) 以多种方式渗透到我们的生活中,使日常任务更轻松、更高效、更个性化。人工智能最重要的应用之一是推荐系统,它已成为我们数字体验不可或缺的一部分。从在流媒体平台上推荐电影到在电子商务网站上推荐产品࿰…...

MySQL的事务特性、事务特性保证和事务隔离级别
事务是指要么所有的操作都成功执行,要么所有的操作都不执行的一组数据库操作。 一、MySQL提供了四个事务特性,即ACID: 1. 原子性(Atomicity):一个事务中的所有操作要么全部提交成功,要么全部回…...

shell脚本----函数
文章目录 一、函数的定义1.1 shell函数:1.2函数如何定义 二、函数的返回值三、函数的传参四、函数变量的作用范围五、函数的递归六、函数库 一、函数的定义 1.1 shell函数: 使用函数可以避免代码重复使用函数可以将大的工程分割为若干小的功能模块,代码的可读性更…...

( 位运算 ) 693. 交替位二进制数 ——【Leetcode每日一题】
❓693. 交替位二进制数 难度:简单 给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。 示例 1: 输入:n 5 输出:true 解释&#…...

http简述
HTTP(Hypertext Transfer Protocol)是一种用于在Web上传输数据的协议。它是Web的基础,使得我们能够在互联网上访问和共享信息。本文将介绍HTTP的基本概念、工作原理、请求和响应、状态码、安全性和未来发展等方面。 一、HTTP的基本概念 HTT…...

一顿饭的事儿,搞懂了Linux5种IO模型
大家好,我是老三,人生有三大难题,事业、爱情,和 ——这顿吃什么! 人在家中躺,肚子饿得响,又到了不得不吃的时候,这顿饭该怎么吃?吃什么呢? Linux里有五种I…...

C#面向对象的概念
C#面向对象的概念 C#是一种面向对象的编程语言,面向对象编程的核心是将程序中的数据和操作封装在一个对象中。下面是一些面向对象的概念: 类(Class):类是用来描述一类对象的属性和方法的模板或蓝图,它定义…...

探索学习和入门使用GitHub Copilot:提升代码开发的新利器
目录 引言1. 什么是GitHub Copilot?2. 入门使用GitHub Copilot3. GitHub Copilot的基础知识4. GitHub Copilot的应用场景结论 在最近的开发工作中,发现了一个比较实用的工具,github copilot,这是一款基于人工智能的代码助手工具&a…...

在字节跳动做了6年软件测试,4月无情被辞,想给划水的兄弟提个醒
先简单交代一下背景吧,某不知名 985 的本硕,17 年毕业加入字节,以“人员优化”的名义无情被裁员,之后跳槽到了有赞,一直从事软件测试的工作。之前没有实习经历,算是6年的工作经验吧。 这6年之间完成了一次…...

常见信号质量问题、危害及其解决方法-信号完整性-过冲、噪声、回勾、边沿缓慢
概述 在电路设计中,“信号”始终是工程师无法绕开的一个知识点。不管是在设计之初,还是在测试环节中,信号质量问题都值得关注。在本文中,主要介绍信号相关的四类问题:信号过冲、毛刺(噪声)、回…...

Java 自定义注解及使用
目录 一、自定义注解1.使用 interface 来定义你的注解2.使用 Retention 注解来声明自定义注解的生命周期3.使用 Target 注解来声明注解的使用范围4.添加注解的属性 二、使用自定义的注解1.将注解注在其允许的使用范围2.使用反射获取类成员变量上的所有注解3.反射获取成员变量上…...

ChatGPT的强化学习部分介绍——PPO算法实战LunarLander-v2
PPO算法 近线策略优化算法(Proximal Policy Optimization Algorithms) 即属于AC框架下的算法,在采样策略梯度算法训练方法的同时,重复利用历史采样的数据进行网络参数更新,提升了策略梯度方法的学习效率。 PPO重要的突…...

JavaWeb ( 八 ) 过滤器与监听器
2.6.过滤器 Filter Filter过滤器能够对匹配的请求到达目标之前或返回响应之后增加一些处理代码 常用来做 全局转码 ,session有效性判断 2.6.1.过滤器声明 在 web.xml 中声明Filter的匹配过滤特征及对应的类路径 , 3.0版本后可以在类上使用 WebFilter 注解来声明 filter-cla…...

Notion Ai中文指令使用技巧
Notion AI 是一种智能技术,可以自动处理大量数据,并从中提取有用的信息。它能够 智能搜索:通过搜索文本和查询结果进行快速访问 自动归档:可以根据关键字和日期自动将内容归档 内容分类:可以根据内容的标签和内容的…...

Linux一学就会——编写自己的shell
编写自己的shell 进程程序替换 替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行…...

编程练习【有效的括号】
给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左…...

Android 音频开发——桌面小部件(七)
对于收音机的车机 APP 开发,一般都有配套的桌面小部件(Widget)开发,这里对小部件的具体实现就不介绍了,这里主要介绍一些桌面(Launcher)中的小部件(Widget)弹出窗口功能实现。 一、功能描述 在小部件上点击按钮,弹出一个有音源选择列表的弹窗,点击其他位置…...

常见的C++包管理
C包管理工具 Conan 是一款免费开源的 C/C语言的依赖项和包管理器 类似于python的anaconda Introduction — conan 2.0.4 documentationconan-io/conan: Conan - The open-source C and C package manager (github.com) CPM cmake集成的 mirrors / cpm-cmake / CPM.cmake GitC…...

基于yolov7开发构建学生课堂行为检测识别系统
yolov7也是一款非常出众的目标检测模型,在我之前的文章中也有非常详细的教程系列的文章,感兴趣的话可以自行移步阅读即可。 《基于YOLOV7的桥梁基建裂缝检测》 《YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程》 《基于YOLOv7融合…...

GPT-4 开始内测32k输入长度的版本了!你收到邀请了吗?
要说现在 GPT-4 最大的问题是什么?可能除了一时拿他没有办法的机器幻觉,就是卡死的输入长度了吧。尽管在一般的对话、搜索的场景里目前普通版本 GPT-4 的 8000 左右的上下文长度或许绰绰有余,但是在诸如内容生成、智能阅读等方面当下基础版的…...