当前位置: 首页 > news >正文

C++11新特性——智能指针——参考bibi《 原子之音》的视频以及ChatGpt

智能指针

  • 一、内存泄露
    • 1.1 内存泄露常见原因
    • 1.2 如何避免内存泄露
  • 二、实例Demo
    • 2.1 文件结构
    • 2.2 Dog.h
    • 2.3 Dog.cpp
    • 2.3 mian.cpp
  • 三、独占式智能指针:unique _ptr
    • 3.1 创建方式
      • 3.1.1 ⭐从原始(裸)指针转换:
      • 3.1.2 ⭐⭐使用 new 关键字直接创建:
      • 3.1.3 ⭐⭐⭐使用 std::make_unique(推荐)
      • 3.1.4 对比:"使用 std::make_unique" 和 "使用 new 关键字直接创建"
    • 3.2 unique的特性
      • 3.2.1 独占所有权:
      • 3.2.2 不可复制、可移动:
      • 3.2.3 自动内存管理:
      • 3.2.4 自定义删除器:
      • 3.2.5 支持数组:
      • 3.2.6 与标准库的兼容性:
  • 四、共享计数指针:shared_ptr
    • 4.1 实例
    • 4.2 shared_ptr 的特性
      • 4.2.1 共享所有权
      • 4.2.2 引用计数:
      • 4.2.3自动内存管理:
      • 4.2.4 线程安全的引用计数:
      • 4.2.5 使用方便:
      • 4.2.6 支持自定义删除器:
      • 4.2.7std::weak_ptr 结合使用:
  • 五、弱引用智能指针:weak_ptr
    • 5.1 循环依赖
    • 5.2 weak_ptr 的特性
      • 5.2.1 不增加引用计数:
      • 5.2.2 防止循环引用:
      • 5.2.3 可以转换为 shared_ptr:
      • 5.2.4 允许检查资源状态:
      • 5.2.5 与 shared_ptr 共享同一控制块:
      • 5.2.6 线程安全:

前言:参加工作两年来,感觉还没咋使用过智能指针,今天还是来总结学习下,以后总有天会用到的

一、内存泄露

C++中,内存泄露指的是程序在运行过程中动态分配内存(使用newmalloc)后,没有释放相应的内存(使用deletefree),导致这些内存空间无法被重新使用,从而造成系统资源的浪费,最终可能导致程序在长时间运行后耗尽可用内存。

1.1 内存泄露常见原因

🎈①忘记释放内存: 在使用动态内存分配后,程序的某些路径可能会遗漏释放内存的代码。

void example() {  int* arr = new int[10]; // 动态分配内存  // ... 使用 arr  // delete[] arr; // 忘记释放内存  
}  

🎈②异常处理: 在分配内存之后如果发生异常,且没有正确处理异常,会导致内存未释放。

void example() {  int* arr = new int[10];  // 假设这里发生异常  throw std::runtime_error("Error");  delete[] arr; // 这一行永远不会被执行  
}  

🎈③指针丢失: 将指针赋值为其他指针时,如果没有先释放原有指针指向的内存,就会造成内存泄漏。

void example() {  int* ptr = new int(42);  ptr = new int(24); // 原来的内存没有释放,造成泄漏  delete ptr; // 只释放了新的内存  
}  

容器管理:在使用标准库容器如std::vectorstd::map等时,注意对存储的指针管理,不要在容器中存储动态分配的原始指针,建议使用智能指针。

1.2 如何避免内存泄露

🎈①使用智能指针C++11引入了std::unique_ptrstd::shared_ptr,它们自动管理内存,减少内存泄露的风险。

#include <memory>  void example() {  std::unique_ptr<int[]> arr(new int[10]); // 使用智能指针  // 不需要手动释放内存,超出作用域时自动释放  
}  

🎈②确保配对:每次使用newmalloc时,确保有相应的deletefree

🎈③RAII(资源获取即初始化):将资源的生命周期与对象的生命周期绑定,避免手动管理内存。

🎈④使用内存检查工具:使用工具如ValgrindAddressSanitizer等进行内存错误检测,帮助发现内存泄露和其它内存管理问题。

通过以上措施,可以大大减少C++程序中的内存泄露问题

二、实例Demo

2.1 文件结构

在这里插入图片描述

2.2 Dog.h

#ifndef DOG_H
#define DOG_H          
/*这里使用条件编译指令防止 重复引用头文件 达到目的:"头文件保护"或"包含保护"也可以用:#pragma once 但是使用该方式更具备兼容性*/
#include<string>
#include<iostream>
class Dog
{
public:Dog(const std::string& name);Dog() = default;/* 这里用了关键字defalut 保留编译器原有的构造函数*/~Dog();void dog_info() const/*使用const 关键字确保该函数只读该类成员*/{std::cout << "U Dog name is:" << this->m_name << std::endl;}std::string get_name () const{return m_name;}void set_name(const std::string& u_name) /*使用const+引用 确保u_name 只读且通过引用方式传值提高效率,避免非必要的拷贝*/{m_name = u_name;}
private:std::string m_name ="bigYellow";
};
#endif

2.3 Dog.cpp

#include "Dog.h"Dog::Dog(const std::string& name):m_name(name)
{std::cout << "构造一根狗子,它叫:" <<m_name <<std::endl;
}Dog::~Dog()
{std::cout << "析构一根狗子,它叫:" << m_name << std::endl;
}

2.3 mian.cpp

#include <iostream>
#include "Dog.h"
#include <memory>/*使用智能指针的头文件*/
using namespace std;int main(int argc,char** argv)
{return 0;
}

三、独占式智能指针:unique _ptr

C++中,智能指针是用来管理动态分配内存的对象,以减少内存泄漏和资源管理的复杂性。独占式指针(std::unique_ptr)是C++11标准库中引入的一种智能指针,它的主要特点是保证对其所管理的资源具有唯一的所有权。

3.1 创建方式

3.1.1 ⭐从原始(裸)指针转换:

通过 std::unique_ptr 的构造函数或 std::unique_ptrreset 方法可以将一个现有的原始(裸)指针转换为 unique_ptr

#include <iostream>
#include "Dog.h"
#include <memory>/*使用智能指针的头文件*/
using namespace std;int main(int argc,char** argv)
{/*方式①:通过原始(裸)指针*/Dog* gua = new Dog("GUA");unique_ptr<Dog> dogUniquePtr(gua);// 将已有的裸指针分配给unique_ptr gua->set_name("XI");gua->dog_info();dogUniquePtr->dog_info();return 0;
}

运行结果:
在这里插入图片描述
我们不妨将这两个指针打印出来:

#include <iostream>
#include "Dog.h"
#include <memory>/*使用智能指针的头文件*/
using namespace std;int main(int argc,char** argv)
{/*方式①:通过原始(裸)指针*/Dog* gua = new Dog("GUA");unique_ptr<Dog> dogUniquePtr(gua);// 将已有的裸指针分配给unique_ptr gua->set_name("XI");gua->dog_info();dogUniquePtr->dog_info();cout << "gua的地址:" << gua << endl;cout << "dogUniquePtr的地址:" << dogUniquePtr.get() << endl;//delete gua;return 0;
}

运行结果:
在这里插入图片描述
两个指针其实都指向了同一块地址,但是我们发现,这块地址任然可以被两个指针操作,这也能叫独占智能指针吗?安全吗这?如果我释放了gua的地址会怎么样?解开上述实例代码最后一行执行:
运行结果:
程序崩溃
在这里插入图片描述
在这里插入图片描述
结论: 这种方式虽然会自动释放内存地址,但是原始(裸)指针仍然可以操作该地址
如果尝试使用已被 std::unique_ptr 释放的内存,会导致未定义行为。
如果你在裸指针上 delete 了内存,而后又让 std::unique_ptr 执行析构,就会发生双重释放的问题,这将会导致程序崩溃。
所以,尽管 std::unique_ptr 是设计为独占式的,但如果需要在你的代码中使用裸指针,必须非常小心,确保不会在裸指针和 std::unique_ptr 之间发生冲突。通常情况下,最佳实践是尽量减少对于裸指针的使用,而是使用智能指针进行内存管理,以确保资源的安全和有效释放。

3.1.2 ⭐⭐使用 new 关键字直接创建:

#include <iostream>
#include "Dog.h"
#include <memory>/*使用智能指针的头文件*/
using namespace std;int main(int argc,char** argv)
{/*方式②:通过new关键字创建*/unique_ptr<Dog> jl{ new Dog("jl") };jl->dog_info();return 0;
}

运行结果:
在这里插入图片描述

3.1.3 ⭐⭐⭐使用 std::make_unique(推荐)

C++14 添加了 std::make_unique 函数,能够更安全地创建 unique_ptr

#include <iostream>
#include "Dog.h"
#include <memory>/*使用智能指针的头文件*/
using namespace std;int main(int argc,char** argv)
{/*方式③:通过td::make_unique(推荐)创建*/unique_ptr<Dog> zzy = std::make_unique<Dog>("zzy");zzy->dog_info();zzy->set_name("bigZZY");zzy->dog_info();return 0;
}

运行结果:
在这里插入图片描述

3.1.4 对比:“使用 std::make_unique” 和 “使用 new 关键字直接创建”

① 可读性和简洁性

  • new 关键字:
std::unique_ptr<int> ptr(new int(42));  
  • std::make_unique:
auto ptr = std::make_unique<int>(42);  

使用 std::make_unique更加简洁、可读,将所有权的转移和对象的构造合并为一行代码,同时也更加简洁,减少了冗余的 new 关键字。

② 安全性
new 关键字:在使用 new 时,可能发生内存分配失败的情况,此时new会抛出 std::bad_alloc 异常。创建 std::unique_ptr 的时候,如果 new 返回 nullptr,则需要开发者在代码中处理这些异常。

std::make_unique:std::make_unique 不会返回 nullptr,而是直接抛出异常,因此代码更加安全并且没有风险(因为 std::unique_ptr 会自动管理内存,即使在构造失败的情况下也不会泄漏内存)。

③ 内存泄漏风险
new 关键字:如果使用 new 创建一个对象,并且由于某种错误(例如异常抛出),没有成功将其指针传递给 std::unique_ptr,则会发生内存泄漏。

std::unique_ptr<int> ptr;  
if (someCondition) {  int* rawPtr = new int(42); // 如果在此之后抛出异常,将会泄漏  ptr.reset(rawPtr);  
} // 可能遇到内存泄漏  

std::make_unique:通过 std::make_unique 创建对象的过程中,确保了不会有内存泄漏的风险,因为std::make_unique 会处理所有权的转移并直接返回 std::unique_ptr

④. 性能
在性能方面,std::make_unique 一般情况下是更优的选择,尽管对于大多数应用程序,它们之间的性能差异可以忽略不计。使用 std::make_unique 可以避免可能的额外操作。

总结:
推荐使用 std::make_unique:由于它提高了可读性、安全性,并且避免了内存泄漏的风险,现代 C++ 的最佳实践是使用 std::make_unique 来创建和管理动态分配的对象。
仅在特定情况下使用 new:当你需要直接获取裸指针或者从其他 API 中传递裸指针时,才可能临时使用 new ,但也要小心管理内存,确保避免内存泄漏。

3.2 unique的特性

3.2.1 独占所有权:

std::unique_ptr 提供独占性所有权语义,这意味着每个 unique_ptr 对象都可以唯一拥有一个动态分配的资源。这样的设计确保了资源不会被多个指针管理,从而避免了双重释放(double free)等问题
结合实例:

	/*特性①:独占所有权*/Dog* gua = new Dog("GUA");unique_ptr<Dog> dogUniquePtr1(gua);//编译错误:不能复制 unique_ptr,因为它是独占的//unique_ptr<Dog> dogUniquePtr2 = dogUniquePtr1;

3.2.2 不可复制、可移动:

std::unique_ptr 不支持复制(copy),即不能使用拷贝构造函数或拷贝赋值运算符,因为这会导致所有权的混淆。相反,它支持移动语义(move),可以通过移动构造和移动赋值将资源的所有权从一个 unique_ptr 转移到另一个:

std::unique_ptr<int> ptr1 = std::make_unique<int>(10);  
std::unique_ptr<int> ptr2 = std::move(ptr1); // ptr1 现在为空,ptr2 拥有资源 

结合实例:

#include <iostream>
#include "Dog.h"
#include <memory>/*使用智能指针的头文件*/
using namespace std;void do_with_pass_dog(unique_ptr<Dog> u_dog)
{cout<<"u_dog 地址是:"<<u_dog.get()<<endl;u_dog->dog_info();
}
int main(int argc,char** argv)
{/*特性②:不可复制、可移动:*/unique_ptr<Dog> zzy = std::make_unique<Dog>("zzy");//do_with_pass_dog(zzy);cout << "zzy 地址是:" << zzy.get() << endl;do_with_pass_dog(std::move(zzy));cout << "zzy 地址是:" << zzy.get() << endl;return 0;
}

运行结果:

在这里插入图片描述
不难发现 通过move,将该智能指针管理的地址,交接给了宁外一个智能指针,这种特性保证了,只有一个智能指针管理一块地址,满足独占指针的特性。

3.2.3 自动内存管理:

std::unique_ptr 的生命周期结束时,它所持有的资源会自动被释放。这种自动释放机制减少了手动管理内存的需要,降低了内存泄漏风险:

{  std::unique_ptr<int> ptr(new int(10)); // ptr 指向新分配的整数  
} //ptr 到达作用域末尾,自动释放内存

3.2.4 自定义删除器:

std::unique_ptr 可以接受一个自定义的删除器,这使得它不仅局限于标准类型的资源管理。例如,可以在资源释放时执行特定的逻辑:

std::unique_ptr<FILE, decltype(&fclose)> filePtr(fopen("example.txt", "r"), fclose);  

3.2.5 支持数组:

std::unique_ptr 可以用来管理动态数组。使用 std::unique_ptr<T[]> 来确保数组的正确释放:

std::unique_ptr<int[]> arrPtr(new int[10]); // 管理动态分配的整型数组 

3.2.6 与标准库的兼容性:

std::unique_ptr 可以与 C++ 标准库中的其他组件良好协作,例如可以用在 STL 容器(如 std::vector)中,从而构建复杂的数据结构:

std::vector<std::unique_ptr<int>> vec;  
vec.push_back(std::make_unique<int>(10));  

四、共享计数指针:shared_ptr

std::shared_ptr C++11 引入的智能指针之一,属于C++标准库中的 <memory> 头文件。它实现了共享所有权的内存管理方式,允许多个 std::shared_ptr实例共同拥有同一个对象。shared_ptr创建了一个计数器与类对象所指的内存相关联 Copy则计数器加一,销毁则计数器减一apiuse_count()
注意:
weak ptr并不拥有所有权
并不能调用-> 和解引用*

4.1 实例

#include <iostream>
#include "Dog.h"
#include <memory>/*使用智能指针的头文件*/
using namespace std;
int main(int argc,char** argv)
{/*计数指针*/// 创建一个 shared_ptr,指向 MyClass 对象  std::shared_ptr<Dog> ptr1 = std::make_shared<Dog>();ptr1->dog_info();// 创建另一个 shared_ptr,指向同一个对象  std::shared_ptr<Dog> ptr2 = ptr1;std::cout << "Reference Count: " << ptr1.use_count() << std::endl;  // 输出: 2  return 0;
}

运行结果:
在这里插入图片描述
①初始化 :std::shared_ptr:使用 std::make_shared 创建一个 shared_ptr ptr1,指向 MyClass 的对象。

②共享所有权:ptr2 是通过拷贝 ptr1 创建的,它们共同拥有同一个 MyClass 对象。此时,引用计数变为。

③引用计数: 使用 use_count() 方法可以查看有多少个shared_ptr在共享同一个对象。

④自动释放资源:ptr1 ptr2在作用域结束后,引用计数降为零,MyClass 对象的内存将自动被释放,调用其析构函数。

4.2 shared_ptr 的特性

4.2.1 共享所有权

多个 std::shared_ptr 实例可以共同管理同一个动态分配的对象,每个指针都有对这个对象的所有权。

#include <iostream>  
#include <memory>  struct Object {  int value;  Object(int v) : value(v) {}  
};  int main() {  std::shared_ptr<Object> ptr1 = std::make_shared<Object>(10);  std::shared_ptr<Object> ptr2 = ptr1; // ptr2 共享 ptr1 的所有权  std::cout << "Value from ptr1: " << ptr1->value << std::endl;  std::cout << "Value from ptr2: " << ptr2->value << std::endl;  return 0;  
}

4.2.2 引用计数:

每个 std::shared_ptr 都维护一个引用计数,用于跟踪有多少个 shared_ptr 实例指向同一个对象。当引用计数降为零时,指向的对象会被自动释放。

#include <iostream>  
#include <memory>  int main() {  std::shared_ptr<int> countPtr = std::make_shared<int>(42);  std::cout << "Reference Count: " << countPtr.use_count() << std::endl; // 输出 1  {  std::shared_ptr<int> anotherPtr = countPtr;  std::cout << "Reference Count: " << countPtr.use_count() << std::endl; // 输出 2  } // anotherPtr 超出作用域  std::cout << "Reference Count: " << countPtr.use_count() << std::endl; // 输出 1  return 0;  
}

4.2.3自动内存管理:

std::shared_ptr 超出作用域或被重置时,会自动释放关联的对象,减少内存泄漏的风险。

#include <iostream>  
#include <memory>  struct Resource {  Resource() { std::cout << "Resource acquired" << std::endl; }  ~Resource() { std::cout << "Resource released" << std::endl; }  
};  int main() {  {  std::shared_ptr<Resource> resourcePtr = std::make_shared<Resource>();  // 资源在这里管理  } // resourcePtr 超出作用域,资源被自动释放  return 0;  
}

4.2.4 线程安全的引用计数:

对引用计数的操作是线程安全的。但需要注意,指向的对象内容本身不是线程安全的。

#include <iostream>  
#include <memory>  
#include <thread>  
void threadFunction(std::shared_ptr<int> ptr) {  std::cout << "Thread value: " << *ptr << std::endl;  
}  
int main() {  std::shared_ptr<int> sharedData = std::make_shared<int>(20);  std::thread t1(threadFunction, sharedData);  std::thread t2(threadFunction, sharedData);  t1.join();  t2.join();  return 0;  
}

4.2.5 使用方便:

可以通过 std::make_shared 来简化 shared_ptr 的创建,同时提高性能(减少内存分配次数)。

#include <iostream>  
#include <memory>  struct Simple {  Simple() { std::cout << "Simple constructed" << std::endl; }  ~Simple() { std::cout << "Simple destructed" << std::endl; }  
};  int main() {  auto simplePtr = std::make_shared<Simple>(); // 自动创建 shared_ptr  return 0; // 自动清理  
}

4.2.6 支持自定义删除器:

可以通过构造函数提供自定义删除器,以定义对象如何被释放,适用于特殊的资源管理需求。

#include <iostream>  
#include <memory>  struct CustomDeleter {  void operator()(int* p) {  std::cout << "Custom delete for " << *p << std::endl;  delete p;  }  
};  int main() {  std::shared_ptr<int> ptr(new int(42), CustomDeleter());  return 0; // 会调用 CustomDeleter  
}

4.2.7std::weak_ptr 结合使用:

可与 std::weak_ptr 一起使用,以打破循环引用的问题,weak_ptr 不增加引用计数。

#include <iostream>  
#include <memory>  struct Node {  std::shared_ptr<Node> next;  ~Node() { std::cout << "Node destroyed" << std::endl; }  
};  int main() {  std::shared_ptr<Node> head = std::make_shared<Node>();  std::weak_ptr<Node> weakHead = head; // weak_ptr 不增加引用计数  head->next = std::make_shared<Node>(); // 创建另一个 Node  std::cout << "Reference count of head: " << head.use_count() << std::endl; // 输出 2  head.reset(); // 释放 shared_ptr  if (auto temp = weakHead.lock()) {  std::cout << "Node is alive." << std::endl;  } else {  std::cout << "Node has been deleted." << std::endl; // 输出  }  return 0;  
}

五、弱引用智能指针:weak_ptr

std::weak_ptrC++ 标准库中的一个智能指针,旨在解决与 std::shared_ptr 相关的循环依赖问题。它提供了一种方式来观察共享对象,但不会增加其引用计数,从而避免了循环引用造成的内存泄漏。

5.1 循环依赖

循环依赖问题通常发生在两个或多个类相互持有对方的引用,这会导致它们之间的引用计数无法归零,从而引发内存泄漏。如下实例:我们创建了两个类 AB,它们互相指向对方的实例。

#include <iostream>  
#include <memory>  class B; // 前向声明  class A {  
public:  std::shared_ptr<B> b; // 拥有者指针  A() { std::cout << "A created" << std::endl; }  ~A() { std::cout << "A destroyed" << std::endl; }  
};  class B {  
public:  std::shared_ptr<A> a; // 拥有者指针  B() { std::cout << "B created" << std::endl; }  ~B() { std::cout << "B destroyed" << std::endl; }  
};  int main() {  std::shared_ptr<A> a = std::make_shared<A>();  std::shared_ptr<B> b = std::make_shared<B>();  a->b = b; // A 拥有 B  b->a = a; // B 拥有 A  return 0; // 当程序结束时,A 和 B 永远无法被销毁  
}

在上述代码中,A 类拥有 B 的一个 shared_ptr,而 B 类也拥有 A 的一个 shared_ptr。这样就形成了一种循环依赖关系:
a 的引用计数为 1(指向 A),但因为它持有 bB 的引用),所以 B 的引用计数也为 1。
b 的引用计数为 1(指向 B),但因为它持有 aA 的引用),所以 A 的引用计数也为 1。
由于这两个类互相持有对方的 shared_ptr,引用计数永远不会归零,导致内存无法释放。

了解决这个问题,我们可以将其中一个 shared_ptr 改为 weak_ptr。通常,持有较少使用的引用或者想要避免循环引用的类会使用 weak_ptr。以下是修改后的代码示例:

#include <iostream>  
#include <memory>  class B; // 前向声明  class A {  
public:  std::shared_ptr<B> b; // 拥有者指针  A() { std::cout << "A created" << std::endl; }  ~A() { std::cout << "A destroyed" << std::endl; }  
};  class B {  
public:  std::weak_ptr<A> a; // 使用 weak_ptr 避免循环引用  B() { std::cout << "B created" << std::endl; }  ~B() { std::cout << "B destroyed" << std::endl; }  
};  int main() {  std::shared_ptr<A> a = std::make_shared<A>();  std::shared_ptr<B> b = std::make_shared<B>();  a->b = b; // A 拥有 B  b->a = a; // B 使用 weak_ptr 指向 A  return 0; // 当程序结束时,A 和 B 将会被正确销毁  
}

5.2 weak_ptr 的特性

5.2.1 不增加引用计数:

std::weak_ptr 拥有一个指向 std::shared_ptr 管理的对象的弱引用。与 std::shared_ptr 不同,它不会增加对象的引用计数。这意味着,std::weak_ptr 仅仅作为对对象的观察者,使用它不会阻止对象的销毁。

#include <iostream>  
#include <memory>  class A {  
public:  A() { std::cout << "A created" << std::endl; }  ~A() { std::cout << "A destroyed" << std::endl; }  
};  int main() {  std::shared_ptr<A> p1 = std::make_shared<A>(); // 创建一个 shared_ptr  std::weak_ptr<A> p2 = p1; // 从 shared_ptr 创建一个 weak_ptr  std::cout << "p1 use count: " << p1.use_count() << std::endl; // 输出: 1  std::cout << "p2 expired: " << p2.expired() << std::endl; // 输出: 0 (false)  return 0;  
}  

5.2.2 防止循环引用:

std::weak_ptr 是解决循环引用问题的关键。通过将某个类的某些指针(通常是指向负责管理资源的类的指针)定义为 weak_ptr,可以打破这种相互依赖关系,允许资源被正确释放。

#include <iostream>  
#include <memory>  class B; // 前向声明  class A {  
public:  std::shared_ptr<B> bPtr; // 使用 shared_ptr  
};  class B {  
public:  std::weak_ptr<A> aPtr; // 使用 weak_ptr 防止循环引用  
};  int main() {  std::shared_ptr<A> a = std::make_shared<A>();  std::shared_ptr<B> b = std::make_shared<B>();  a->bPtr = b;  b->aPtr = a; // 不会引起循环引用  return 0; // 程序结束时 A 和 B 的资源会被正确释放  
}  

5.2.3 可以转换为 shared_ptr:

std::weak_ptr 提供了一个 lock() 方法,可以将其转换为 std::shared_ptr,如果原始对象仍然存在(即其引用计数大于零),lock() 方法会返回一个有效的 std::shared_ptr。如果对象已被销毁,则返回一个空的 shared_ptr

#include <iostream>  
#include <memory>  class A {  
public:  A() { std::cout << "A created" << std::endl; }  ~A() { std::cout << "A destroyed" << std::endl; }  
};  int main() {  std::shared_ptr<A> p1 = std::make_shared<A>();  std::weak_ptr<A> p2 = p1;  std::shared_ptr<A> p3 = p2.lock(); // 尝试获取 shared_ptr  if (p3) {  std::cout << "p3 acquired" << std::endl; // 将会输出  }  p1.reset(); // 释放 p1 的资源  std::shared_ptr<A> p4 = p2.lock(); // 尝试再次获取 shared_ptr  if (!p4) {  std::cout << "p4 is null" << std::endl; // 将会输出  }  return 0;  
}  

5.2.4 允许检查资源状态:

通过使用 std::weak_ptr,可以检查资源对象的状态。可以使用 expired() 方法来检查指向的对象是否已经被销毁。如果返回 true,则表明对象已不再存在。

#include <iostream>  
#include <memory>  class A {  
public:  A() { std::cout << "A created" << std::endl; }  ~A() { std::cout << "A destroyed" << std::endl; }  
};  int main() {  std::weak_ptr<A> p1;  {  std::shared_ptr<A> p2 = std::make_shared<A>();  p1 = p2; // p1 指向 p2  std::cout << "p1 expired: " << p1.expired() << std::endl; // 输出: 0 (false)  }  std::cout << "p1 expired after exiting scope: " << p1.expired() << std::endl; // 输出: 1 (true)  return 0;  
}  

5.2.5 与 shared_ptr 共享同一控制块:

std::weak_ptr 与其对应的 std::shared_ptr 共享同一个控制块,这个控制块保存着引用计数和其他状态信息。这允许 weak_ptr 检查原始对象的状态。

#include <iostream>  
#include <memory>  class A {  
public:  A() { std::cout << "A created" << std::endl; }  ~A() { std::cout << "A destroyed" << std::endl; }  
};  int main() {  std::shared_ptr<A> p1 = std::make_shared<A>();  std::weak_ptr<A> p2 = p1; // p2 和 p1 共享同一控制块  std::cout << "p1 use count: " << p1.use_count() << std::endl; // 输出: 1  std::cout << "p2 use count (via shared_ptr): " << p2.lock().use_count() << std::endl; // 输出: 1  return 0;  
}

5.2.6 线程安全:

std::weak_ptr std::shared_ptr 的操作是线程安全的。可以在多个线程中安全地访问和管理这些智能指针。

#include <iostream>  
#include <memory>  
#include <thread>  
#include <vector>  
#include <chrono>  class A {  
public:  A() { std::cout << "A created" << std::endl; }  ~A() { std::cout << "A destroyed" << std::endl; }  
};  void threadFunction(std::weak_ptr<A> weakPtr) {  // 尝试从 weak_ptr 获取 shared_ptr  std::shared_ptr<A> sharedPtr = weakPtr.lock();  if (sharedPtr) {  std::cout << "Thread accessing object" << std::endl;  } else {  std::cout << "Object already destroyed" << std::endl;  }  
}  int main() {  std::shared_ptr<A> p1 = std::make_shared<A>();  // 创建一个 weak_ptr 来观察 p1  std::weak_ptr<A> p2 = p1;  // 启动多个线程来访问 p2  std::vector<std::thread> threads;  for (int i = 0; i < 5; ++i) {  threads.emplace_back(threadFunction, p2);  }  // 等待一段时间然后重置 p1,模拟对象的销毁  std::this_thread::sleep_for(std::chrono::milliseconds(100));  p1.reset(); // 释放 p1 的资源  // 等待所有线程完成  for (auto& t : threads) {  t.join();  }  return 0;  
}  

🎈①代码说明:
class A:定义了一个简单的类 A,其构造和析构函数会打印消息,以便我们跟踪对象的创建与销毁过程。
threadFunction:每个线程执行的函数,尝试通过调用 weakPtr.lock() 获取对象的 shared_ptr
如果成功获取 shared_ptr,表示对象仍然存在,线程可以安全访问该对象。
如果对象已经被销毁,lock() 返回空指针,线程会打印相关消息。

🎈②主函数中的逻辑:
创建一个 std::shared_ptr<A> 实例 p1,并从中获得一个 std::weak_ptr<A> 实例 p2
启动多个线程,每个线程都尝试访问同一个 weak_ptr
主线程等待一段时间,然后重置 p1,模拟对象的销毁。
等待所有线程结束执行。

🎈③线程安全注意事项:
使用 std::weak_ptr 来访问 std::shared_ptr,避免了因对象销毁而引起的悬挂指针。
weak_ptr.lock() 是线程安全的,可以安全地用于多个线程同时访问的场景。
在整个程序运行过程中,你将看到某些线程能够访问对象,而在对象被销毁后,其他线程则会看到对象已经被销毁。

相关文章:

C++11新特性——智能指针——参考bibi《 原子之音》的视频以及ChatGpt

智能指针 一、内存泄露1.1 内存泄露常见原因1.2 如何避免内存泄露 二、实例Demo2.1 文件结构2.2 Dog.h2.3 Dog.cpp2.3 mian.cpp 三、独占式智能指针:unique _ptr3.1 创建方式3.1.1 ⭐从原始(裸)指针转换&#xff1a;3.1.2 ⭐⭐使用 new 关键字直接创建&#xff1a;3.1.3 ⭐⭐⭐…...

“微软蓝屏”全球宕机,敲响基础软件自主可控警钟

上周五&#xff0c;“微软蓝屏”“感谢微软 喜提假期”等词条冲上热搜&#xff0c;全球百万打工人受此影响&#xff0c;共同见证这一历史性事件。据微软方面发布消息称&#xff0c;旗下Microsoft 365系列服务出现访问中断。随后在全球范围内&#xff0c;包括企业、政府、个人在…...

【Linux C | 网络编程】进程间传递文件描述符socketpair、sendmsg、recvmsg详解

我们的目的是&#xff0c;实现进程间传递文件描述符&#xff0c;是指 A进程打开文件fileA,获得文件描述符为fdA&#xff0c;现在 A进程要通过某种方法&#xff0c;传递fdA&#xff0c;使得另一个进程B&#xff0c;获得一个新的文件描述符fdB&#xff0c;这个fdB在进程B中的作用…...

高并发内存池(六)Page Cache回收功能的实现

当Page Cache接收了一个来自Central Cache的Span&#xff0c;根据Span的起始页的_pageId来对前一页所对应的Span进行查找&#xff0c;并判断该Span&#xff0c;是否处于使用状态&#xff0c;从而看是否可以合并&#xff0c;如果可以合并继续向前寻找。 当该Span前的空闲Span查…...

浅析JWT原理及牛客出现过的相关面试题

原文链接&#xff1a;https://kixuan.github.io/posts/f568/ 对jwt总是一知半解&#xff0c;而且项目打算写个关于JWT登录的点&#xff0c;所以总结关于JWT的知识及网上面试考察过的点 参考资料&#xff1a; Cookie、Session、Token、JWT_通俗地讲就是验证当前用户的身份,证明-…...

Spring AI (五) Message 消息

5.Message 消息 在Spring AI提供的接口中&#xff0c;每条信息的角色总共分为三类&#xff1a; SystemMessage&#xff1a;系统限制信息&#xff0c;这种信息在对话中的权重很大&#xff0c;AI会优先依据SystemMessage里的内容进行回复&#xff1b; UserMessage&#xff1a;用…...

【windows Docker desktop】在git bash中报错 docker: command not found 解决办法

【windows Docker desktop】在git bash中报错 docker: command not found 解决办法 1. 首先检查在windows中环境变量是否设置成功2. 检查docker在git bash中环境变量是否配置3. 重新加载终端配置4. 最后在校验一下是否配置成功 1. 首先检查在windows中环境变量是否设置成功 启…...

02.FreeRTOS的移植

文章目录 FreeRTOS移植到STM32F103ZET6上的详细步骤1. 移植前的准备工作2. 添加FreeRTOS文件3. 修改SYSTEM文件4. 修改中断相关文件5. 修改FreeRTOSConfig.h文件6. 可选步骤 FreeRTOS移植到STM32F103ZET6上的详细步骤 1. 移植前的准备工作 **基础工程&#xff1a;**内存管理部…...

【个人笔记】一个例子理解工厂模式

工厂模式优点&#xff1a;创建时类名过长或者参数过多或者创建很麻烦等情况时用&#xff0c;可以减少重复代码&#xff0c;简化对象的创建过程&#xff0c;避免暴露创建逻辑&#xff0c;也适用于需要统一管理所有创建对象的情况&#xff0c;比如线程池的工厂类Executors 简单工…...

【C语言】数组栈的实现

栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&#…...

kafka 各种选举过程

一、kafka 消费者组协调器 如何选举 Kafka 中的消费者组协调器&#xff08;Group Coordinator&#xff09;是通过以下步骤选举的&#xff1a; 分区映射&#xff1a; Kafka 使用一个特殊的内部主题 __consumer_offsets 来存储消费者组的元数据。该主题有多个分区&#xff0c;每…...

树与二叉树【数据结构】

前言 之前我们已经学习过了各种线性的数据结构&#xff0c;顺序表、链表、栈、队列&#xff0c;现在我们一起来了解一下一种非线性的结构----树 1.树的结构和概念 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一…...

简单几步,把浏览器书签转换成导航网页

废话不多说直奔主题上干货 Step 1 下载浏览器书签 1&#xff0c;电脑浏览器点击下载Pintree Pintree 是一个开源项目&#xff0c;旨在将浏览器书签导出成导航网站。通过简单的几步操作&#xff0c;就可以将你的书签转换成一个美观且易用的导航页面。 2. 安装 Pintree B…...

Mac安装Hoomebrew与升级Python版本

参考 mac 安装HomeBrew(100%成功)_mac安装homebrew-CSDN博客 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 安装了Python 3.x版本&#xff0c;你可以使用以下命令来设置默认的Python版本&#xff1a; # 首先找到新安…...

代码审计:Bluecms v1.6

代码审计&#xff1a;Bluecms v1.6 漏洞列表如下(附Exp)&#xff1a; 未完待续… 1、include/common.fun.php->getip()存在ip伪造漏洞 2、ad_js.php sql注入漏洞 Exp:view-source:http://127.0.0.3/bluecms/ad_js.php?ad_id12%20UNION%20SELECT1,2,3,4,5,6,database() 3、…...

谷粒商城实战笔记-59-商品服务-API-品牌管理-使用逆向工程的前后端代码

文章目录 一&#xff0c; 使用逆向工程生成的代码二&#xff0c;生成品牌管理菜单三&#xff0c;几个小问题 在本次的技术实践中&#xff0c;我们利用逆向工程的方法成功地为后台管理系统增加了品牌管理功能。这种开发方式不仅能快速地构建起功能模块&#xff0c;还能在一定程度…...

如何利用Jenkins自动化管理、部署数百个应用

目录 1. Jenkins 安装与部署步骤 1.1 系统要求 1.2 安装步骤 1.2.1 Windows 系统 1.2.2 CentOS 系统 1.3 初次配置 2. Gradle 详细配置方式 2.1 安装 Gradle 2.1.1 Windows 系统 2.1.2 CentOS 系统 2.2 配置 Jenkins 中的 Gradle 3. JDK 详细配置方式 3.1 安装 JD…...

Java之归并排序

归并排序 归并排序(Merge Sort)算法&#xff0c;使用的是分治思想。分治&#xff0c;顾名思义&#xff0c;就是分而治之&#xff0c;将一个大问题分解成小的子问题来解决。小的子问题解决了&#xff0c;大问题也就解决了。 核心源码: mergeSort(m->n) merge(mergeSort(m-&g…...

了解ChatGPT API

要了解如何使用 ChatGPT API&#xff0c;可以参考几个有用的资源和教程&#xff0c;这些资源能帮助你快速开始使用 API 进行项目开发。下面是一些推荐的资源&#xff1a; OpenAI 官方文档&#xff1a; 访问 OpenAI 的官方网站可以找到 ChatGPT API 的详细文档。这里包括了 API …...

EasyAnimate - 阿里开源视频生成项目,国产版Sora,高质量长视频生成 本地一键整合包下载

EasyAnimate是阿里云人工智能平台PAI自主研发的DiT-based视频生成框架&#xff0c;它提供了完整的高清长视频生成解决方案&#xff0c;包括视频数据预处理、VAE训练、DiT训练、模型推理和模型评测等。在预训练模型的基础上&#xff0c;EasyAnimate可通过少量图片的LoRA微调来改…...

7月23日JavaSE学习笔记

异常&#xff1a; 程序中一些程序处理不了的特殊情况 异常类 Exception 继承自 Throwable 类&#xff08;可抛出的&#xff09; Throwable继承树 Error&#xff1a;错误/事故&#xff0c;Java程序无法处理&#xff0c;如 OOM内存溢出错误、内存泄漏...会导出程序崩溃 常见的…...

Linux——DNS服务搭建

&#xff08;一&#xff09;搭建nginx 1.首先布置基本环境 要求能够ping通外网&#xff0c;有yum源 2.安装nginx yum -y install nginx 然后查看验证 3.修改网页配置文件 修改文件&#xff0c;任意编写内容&#xff0c;然后去物理机测试 &#xff08;二&#xff09;创建一…...

C#中的wpf基础

在WPF中&#xff0c;Grid 是一种非常强大的布局控件&#xff0c;用于创建网格布局。它允许你将界面划分为行和列&#xff0c;并将控件放置在这些行和列中。 以下是一些关键点和示例&#xff0c;帮助你理解 WPF 中的 Grid&#xff1a; 基本属性 RowDefinitions&#xff1a;定义…...

基于微信小程序+SpringBoot+Vue的刷题系统(带1w+文档)

基于微信小程序SpringBootVue的刷题系统(带1w文档) 基于微信小程序SpringBootVue的刷题系统(带1w文档) 本系统是将网络技术和现代的管理理念相结合&#xff0c;根据试题信息的特点进行重新分配、整合形成动态的、分类明确的信息资源&#xff0c;实现了刷题的自动化&#xff0c;…...

SSH -i的用法

缘起 今天使用ssh -i指定私钥时遇到以下错误&#xff1a; WARNING: UNPROTECTED PRIVATE KEY FILE! Permissions 0644 for /home/ken/.ssh/my.pem are too open. It is required that your private key files are NOT accessible by others. This private key will b…...

小白学习webgis的详细路线

推荐打开boss直聘搜索相关岗位&#xff0c;查看岗位要求&#xff0c;对症下药是最快的。 第一阶段&#xff1a;基础知识准备 计算机基础 操作系统&#xff1a;理解Windows、Linux或macOS等操作系统的基本操作&#xff0c;学会使用命令行界面。网络基础&#xff1a;掌握TCP/I…...

使用ChatGPT来撰写和润色学术论文的教程(含最新升级开通ChatGpt4教程)​​

现在有了ChatGPT4o更加方便了, 但次数太少了 想要增加次数可以考虑升级开桶ChatGpt4​​ &#xff08; OPENAI4 可以减2刀&#xff09; 一、引言 在学术研究中&#xff0c;撰写高质量的论文是一项重要的技能。本教程将介绍如何利用ChatGPT来辅助完成从论文构思到润色的全过程…...

常见的 HTTP 状态码分类及说明

HTTP 响应状态码&#xff08;HTTP status code&#xff09;&#xff0c;表示服务器对请求的处理结果。常见的 HTTP 状态码有以下几类&#xff1a; 1xx: 信息响应 (Informational Responses) 100 Continue: 请求已收到&#xff0c;客户端应继续发送请求的其余部分。101 Switch…...

Leetcode700.二叉搜索树中搜索具体值

二叉搜索树的定义&#xff1a; 一颗空树或者具有以下性质的二叉树&#xff1a; 若任意节点的左子树不空&#xff0c;则左子树上所有节点的值均小于它的根节点的值&#xff1b;若任意节点的右子树不空&#xff0c;则右子树上所有节点的值均大于它的根节点的值&#xff1b;任意节…...

自动导入unplugin-auto-import+unplugin-vue-components

文章介绍 接下来将会以Vite Vue3 TS的项目来举例实现 在我们进行项目开发时&#xff0c;无论是声明响应式数据使用的ref、reactive&#xff0c;或是各种生命周期&#xff0c;又或是computed、watch、watchEffect、provide-inject。这些都需要前置引入才能使用&#xff1a; …...

Conda修改包/虚拟环境储存目录

Conda修改包/虚拟环境储存目录 关键字样例 关键字 通过conda config --show [key]可以查看某个配置的值&#xff0c;[key]留空可以查看所有配置 其中&#xff1a; envs-dirs 存放虚拟环境的储存目录pkgs_dirs 包的目录 通过conda config --add [key] [value]可以为配置添加值…...

Live555源码阅读笔记:哈希表的实现(C++)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

警务平台app

智慧公安以大数据、云计算、人工智能、物联网和移动互联网技术为支撑&#xff0c;以“打、防、管、控”为目的&#xff0c;综合研判为核心&#xff0c;共享信息数据资源&#xff0c;融合业务功能&#xff0c;构建公安智慧大数据平台&#xff0c;实现公安信息数字化、网络化和智…...

Java代理模式详解

Java代理模式详解 概念 代理模式是一种设计模式&#xff0c;为其他对象提供一种代理以控制对这个对象的访问。在某些情况下&#xff0c;一个对象不适合或者不能直接引用另一个对象&#xff0c;而代理对象可以在客户端和目标对象之间起到中介的作用。在Java中&#xff0c;代理…...

docker centos镜像 npm安装包时报错“npm ERR! code ECONNRESET”

1.采用新的镜像地址 npm config set registry https://registry.npmmirror.com2.清理缓存 npm cache clean --force3.安装yarn npm install -g yarn4. 安装模块 在node_modules 同级目录执行下面命令&#xff1a; yarn add napi-build-utils env-paths express ejs cors …...

Angular中component和directive的区别?

在Angular中&#xff0c;Component和Directive都是重要的构建块&#xff0c;用于构建和组织应用程序的UI。然而&#xff0c;它们有不同的用途和特点。以下是Component和Directive的主要区别&#xff1a; Component&#xff08;组件&#xff09; 1、定义&#xff1a;Component…...

Unity 资源 之 Pop It 3D 解压玩具与双人AI游戏 Unity 资源包分享

精彩呈现&#xff1a;Pop It 3D 解压玩具与双人AI游戏 Unity 资源包分享 一、Pop It 3D 解压玩具的魅力二、双人游戏的互动乐趣三、Unity 游戏资源包的优势四、如何获取资源包 亲爱的游戏爱好者们&#xff0c;今天为大家带来一款令人兴奋的游戏资源——Pop It 3D 解压玩具双人带…...

linux离线安装mysql8(单机版)

文章目录 一、检查服务器是否有残留mysql资源&#xff0c;有的话就全删除1.1、查询mysql已安装的相关依赖&#xff1a;1.2、查找含有MySQL的目录 二、安装2.1、上传mysql安装包到文件夹下并解压2.2、移动及重命名2.3、mysql用户2.4、配置mysql所需的my.cnf文件2.5、给my.cnf配置…...

【Python】快速创建一个简易 HTTP 服务器(http.server)

目录 官方文档安装教程用命令行创建编写代码创建 实例 官方文档 http.server 警告&#xff1a; http.server 不推荐用于生产环境。它仅仅实现了 basic security checks 的要求。 安装 Python3 内置标准模块&#xff0c;无需安装。&#xff08;在之前的 Python2 版本名称是 Si…...

随着软件开发方法的不断演进,Cobol 程序如何适应敏捷开发和持续集成/持续部署(CI/CD)的流程?

Cobol是一种古老的编程语言&#xff0c;最初设计用于商业数据处理。虽然它不是为敏捷开发和CI/CD流程而设计的&#xff0c;但仍然可以通过一些技术和方法来使其与这些现代开发流程兼容。 以下是一些方法可以帮助Cobol程序适应敏捷开发和CI/CD流程&#xff1a; 拆分和模块化&am…...

nodejs - MongoDB 学习笔记

一、简介 1、MongoDB 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 2、数据看是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的应用程序。 3、数据库的作用 主要作用是 管理数据…...

photoshop学习笔记——移动工具

移动工具&#xff0c;可以对图层进行移动&#xff0c;快捷键 V 使用的素材已经放上了&#xff0c;直接下载即可 按住ctrl 可以自动选取&#xff0c;鼠标点击哪个对象&#xff0c;自动选中哪个图层 按住 shift 校正角度&#xff08;只能沿着直线移动&#xff09; 按住 alt 拖…...

HarmonyOS 质量、测试、上架速浏

1.应用质量要求&#xff1a; 1. 应用体验质量建议: 功能数据完备 功能完备 数据完备 基础体验要求 基础约束 兼容性 稳定性 性能 功耗 安全…...

TS的访问修饰符有哪些

如果你和我一样是从强类型语言(如C、C#、Java)转过来的&#xff0c;相信你会一眼就知道是什么 public&#xff08;默认&#xff09; - 全部可访问 protected - 自己和派生类可访问 private - 只有自己可访问 废话不多说&#xff0c;上代码&#xff1a; class Person {publ…...

网络安全之扫描探测阶段攻防手段(二)

扫描探测 扫描探测阶段是攻击者对目标网络进行深入了解的关键步骤&#xff0c;同时也是防御者识别潜在威胁和加强安全防护的机会。 攻击端&#xff1a;技术原理和工具 端口扫描&#xff1a; 原理&#xff1a;攻击者使用端口扫描工具来识别目标网络中开放的端口&#xff0c;这…...

C++:泛型算法了解

什么是泛型算法 泛型算法是C标准模板库&#xff08;STL&#xff09;中的一部分&#xff0c;它们表示的是可以用于不同类型的元素和多种容器类型的一些经典算法的公共接口。这些算法之所以被称为“泛型”&#xff0c;是因为它们可以操作在多种容器类型上&#xff0c;包括但不限…...

基于bert的自动对对联系统

目录 概述 演示效果 核心逻辑 使用方式 1.裁剪数据集 根据自己的需要选择 2.用couplet数据集训练模型 模型存储在model文件夹中 3.将模型转换为ONNX格式 4.打开index.html就可以在前端使用此自动对对联系统了。 本文所涉及所有资源均在传知代码平台可获取。 概述 这个生成器利用…...

js-vue中多个按钮状态选中类似于复选框与单选框实现

1.vue中多个按钮状态选中类似于复选框 在Vue中处理多个按钮的选中状态切换&#xff0c;通常我们会利用Vue的响应式数据系统来追踪每个按钮的选中状态。 html <div id"app"> <button v-for"button in buttons" :key"button.id" :c…...

ObservableCollection新增数据前判断数据是否存在

public class MyDataModel {public int Id { get; set; }public string Name { get; set; }}public static void Main(){// 创建 ObservableCollectionObservableCollection<MyDataModel> myDataCollection new ObservableCollection<MyDataModel>{new MyDataMode…...

DBus快速入门

DBus快速入门 参考链接&#xff1a; 中文博客&#xff1a; https://www.e-learn.cn/topic/1808992 https://blog.csdn.net/u011942101/article/details/123383195 https://blog.csdn.net/weixin_44498318/article/details/115803936 https://www.e-learn.cn/topic/1808992 htt…...