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() 是线程安全的,可以安全地用于多个线程同时访问的场景。
在整个程序运行过程中,你将看到某些线程能够访问对象,而在对象被销毁后,其他线程则会看到对象已经被销毁。

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

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

相关文章

Idea中连接MS SQL Server报错:驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接

一、错误重现 报错如下&#xff1a; [08S01] 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to request…

leetcode日记(59)简化路径

&#xff08;小杯文字描述…看了好一会才看懂题目要求&#xff09;题目是标准化路径&#xff0c;就是将多个‘/’优化为一个、将最后一个‘/’去掉、将‘/../’和‘/./’去掉&#xff0c;将原路径转化为实际想表达的最终路径。 有点像单纸带图灵机&#xff0c;需要依次遍历字母…

Windows系统安全加固方案:快速上手系统加固指南 (下)

这里写目录标题 一、概述二、IP协议安全配置启用SYN攻击保护 三、文件权限3.1 关闭默认共享3.2 查看共享文件夹权限3.3 删除默认共享 四、服务安全4.1禁用TCP/IP 上的NetBIOS4.2 ### 禁用不必要的服务 五、安全选项5.1启动安全选项5.2禁用未登录前关机 六、其他安全配置**6.1防…

《JavaEE》----2.<多线程的简介创建Thread类>

前言&#xff1a; 大家好&#xff0c;我目前在学习java。我准备利用这个暑假&#xff0c;来复习之前学过的内容&#xff0c;并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区进行讨论&#xff01;&#xff01;&#xff01; 喜欢我文…

【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究“(中)

【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究”(中) 大家好 我是寸铁&#x1f44a; 【深度学习】“复杂场景下基于深度学习的卷积神经网络在鸟类多类别识别中的模型设计与性能优化研究”(中)✨ 喜欢的小伙伴可以点点关注 &a…

一个网站搞定Adobe系列软件下载安装,良心网站!

Adobe系列软件几乎是每个办公职场人都会用到的软件&#xff0c;比如PDF&#xff0c;PS&#xff0c;AI&#xff0c;PE&#xff0c;PR等&#xff0c;不管你是设计图片&#xff0c;制作编辑音频还是视频&#xff0c;Adobe都有对应的软件。但是对于大部分用户来说&#xff0c;Adobe…

Linux网络:传输层TCP协议(四)拥塞控制及延迟应答

目录 一、拥塞控制 二、延迟应答 一、拥塞控制 虽然 TCP 拥有滑动窗口这个大杀器机制来根据具体情况对发送的数据大小和速度进行实时控制, 能够高效并且可靠的发送大量的数据. 但是如果在双方建立好连接后的刚开始阶段就发送大量的数据。仍然可能引发一些问题. 因为同一个网…

怎么给PDF文件加密码?关于PDF文件加密的四种方法推荐

怎么给PDF文件加密码&#xff1f;给PDF文件加上密码是保护文件安全的一种重要方法&#xff0c;特别是当需要在不受授权的访问下保护敏感信息时。这个过程不仅仅是简单地设置密码&#xff0c;而是涉及到对文档内容和访问控制的深思熟虑。加密PDF文件可以有效防止未经授权的用户查…

杂谈(杂鱼谈论c语言)——2.大小端字节序

⼤⼩端字节序和字节序判断 当我们了解了整数在内存中存储后&#xff0c;我们调试看⼀个细节&#xff1a; #include <stdio.h> int main() {int a 0x11223344;return 0; } 调试的时候&#xff0c;我们可以看到在a中的 0x11223344 这个数字是按照字节为单位&#xff0c;…

creality ender2的3D打印经验教训

创想云-3D打印模型库-一体化3D打印平台 1.开机后要放一张白纸进行检查&#xff0c;看看打印头立平台的距离&#xff0c;如果太近&#xff0c;会灼烧平台&#xff0c;会造成下面的结果&#xff1a; 2.下载模型&#xff0c;可以在线切片&#xff0c;要看看是否要支撑 没有支撑可…

vulntarget-a

实际部署之后的win7 ip: 192.168.127.128 具体攻击过程如下 win7 扫描服务 使用fscan扫描win 7中的服务以及漏洞 ./fscan -h 192.168.127.128 扫出来一个ms17-010以及通达oa的漏洞&#xff0c;既然有永恒之蓝的&#xff0c;直接上MSF就行了 msf6 > search ms17-010 msf6…

Bouncy Castle集成SM2与SM3

在Bouncy Castle库中&#xff0c;SM2和SM3是两种分别用于非对称加密和数字签名的密码算法&#xff0c;它们也可以结合使用&#xff0c;形成一种高安全性的加密签名方案&#xff0c;即SM2withSM3。以下是对SM2SM3的详细解释&#xff1a; 一、SM2算法 SM2是一种由中国国家密码管…

学前教育优化算法,原理详解,MATLAB代码免费获取

学前教育优化算法&#xff08;Preschool Education Optimization Algorithm&#xff0c;PEOA)是一种受学前教育过程中孩童的活动行为启发而提出的元启发式优化算法。学前教育在儿童的早期发展中起着至关重要的作用&#xff0c;并为他们未来的学习旅程奠定基础。作为幼儿学习者发…

【JavaScript】详解Day.js:轻量级日期处理库的全面指南

文章目录 一、Day.js简介1. 什么是Day.js&#xff1f;2. 安装Day.js 二、Day.js的基本用法1. 创建日期对象2. 格式化日期3. 解析日期字符串4. 操作日期5. 比较日期 三、Day.js的高级功能1. 插件机制2. 国际化支持 四、实际应用案例1. 事件倒计时2. 日历应用 在JavaScript开发中…

Python | ValueError: could not convert string to float: ‘example’

Python | ValueError: could not convert string to float: ‘example’ 在Python编程中&#xff0c;类型转换是一个常见的操作。然而&#xff0c;当尝试将一个字符串转换为浮点数时&#xff0c;如果字符串的内容不是有效的浮点数表示&#xff0c;就会遇到“ValueError: could…

labview四字节转浮点数

1.labview四字节转浮点数 2.Labview怎么把串口接收到的数据转换成浮点数&#xff1f; Labview怎么把串口接收到的数据转换成浮点数&#xff1f;

如何跨越 LangChain 应用研发的最后一公里

说 [LangChain] 是现在最流行的 AI 应用开发框架&#xff0c;应该没有人出来反对吧。LangChain 的出现极大地简化了基于大型语言模型&#xff08;LLM&#xff09;的 AI 应用构建难度&#xff0c;如果把 AI 应用比作一个人的话&#xff0c;那么 LLM 相当于这个人的“大脑”&…

基于vue-grid-layout插件(vue版本)实现增删改查/拖拽自动排序等功能(已验证、可正常运行)

前端时间有个需求&#xff0c;需要对33&#xff08;不一定&#xff0c;也可能多行&#xff09;的卡片布局&#xff0c;进行拖拽&#xff0c;拖拽过程中自动排序&#xff0c;以下代码是基于vue2&#xff0c;可直接运行&#xff0c;报错可评论滴我 部分代码优化来自于GPT4o和Clau…

78.WEB渗透测试-信息收集-框架组件识别利用(2)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;77.WEB渗透测试-信息收集-框架组件识别利用&#xff08;1&#xff09; shiro&#xff1a;…

支持向量机 及其分类案例详解(附Python 代码)

支持向量机分类器预测收入等级 我们将构建一个支持向量机&#xff08;SVM&#xff09;分类器&#xff0c;以预测一个人基于14个属性的收入等级。我们的目标是判断收入是否高于或低于每年$50,000。因此&#xff0c;这是一个二元分类问题。我们将使用在此处可用的人口普查收入数…