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.rhkb.cn/news/385579.html

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

相关文章

如何录制电脑内部声音?全方位介绍电脑录音软件:8款在线录音!(2024重新整理)

如何录制电脑内部声音&#xff1f;不管是娱乐圈还是现实生活&#xff0c;【录音】这个功能的重要性不言而喻。而电脑录音已在影视配音、音视频剪辑、会议记录、在线教育等多个领域发光发热&#xff01; 本文将为您推荐8款电脑录音软件&#xff0c;并详细介绍电脑录音的多种方式…

electron 网页TodoList工具打包成win桌面应用exe

参考&#xff1a; electron安装&#xff08;支持win、mac、linux桌面应用&#xff09; https://blog.csdn.net/weixin_42357472/article/details/140643624 TodoList工具 https://blog.csdn.net/weixin_42357472/article/details/140618446 electron打包过程&#xff1a; 要将…

51单片机-第五节-串口通信

1.什么是串口&#xff1f; 串口是通讯接口&#xff0c;实现两个设备的互相通信。 单片机自带UART&#xff0c;其中引脚有TXD发送端&#xff0c;RXD接收端。且电平标准为TTL&#xff08;5V为1,0V为0&#xff09;。 2.常见电平标准&#xff1a; &#xff08;1&#xff09;TTL电…

景区AR导航营销系统:技术解决方案与实施效益分析

随着旅游市场的竞争日益激烈&#xff0c;景区需要不断创新以吸引游客。景区 AR 导航将虚拟画面与现实场景相结合&#xff0c;为游客提供了更加直观、生动的导航服务。对于景区而言&#xff0c;这一创新技术无疑是吸引游客目光、提升景区知名度的有力武器。通过独特的 AR 导航体…

RockyLinux 9 PXE Server bios+uefi 自动化部署 RockLinux 8 9

pxe server 前言 PXE&#xff08;Preboot eXecution Environment&#xff0c;预启动执行环境&#xff09;是一种网络启动协议&#xff0c;允许计算机通过网络启动而不是使用本地硬盘。PXE服务器是实现这一功能的服务器&#xff0c;它提供了启动镜像和引导加载程序&#xff0c;…

一款基于Cortex-M0+的单片机音频编解码 - CJC2100

USBCodec芯片可以对数字音频信号进行多种处理&#xff0c;例如增加音量、均衡调节、音效处理等。这些处理可以通过耳机的控制按钮来实现&#xff0c;让用户可以根据自己的喜好来调整音频效果。USBCodec芯片还可以控制噪声和失真的水平&#xff0c;以提供高品质的音频输出。噪声…

机器学习笔记-02-基础线性算法认识(问题-解答自查版)

前言 以下问题以Q&A形式记录&#xff0c;基本上都是笔者在初学一轮后&#xff0c;掌握不牢或者频繁忘记的点 Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系&#xff0c;也适合做查漏补缺和复盘。 本文可以让读者用作自查&#xff0c;答案在后面&#xff0…

2024世界技能大赛某省选拔赛“网络安全项目”B模块--操作系统取证解析

2024世界技能大赛某省选拔赛“网络安全项目”B模块--操作系统取证解析 任务一、操作系统取证解析:总结:任务一、操作系统取证解析: A 集团某电脑系统被恶意份子攻击并控制,怀疑其执行了破坏操作,窃取了集团内部的敏感信息,现请分析 A 集团提供的系统镜像和内存镜像,找到…

机会性加密技术:网络安全的新趋势

在当今数字化时代&#xff0c;网络安全已成为各行各业不可忽视的重要议题。随着网络攻击手段的不断演进&#xff0c;传统的加密方式已难以满足复杂多变的安全需求。机会性加密技术&#xff08;Opportunistic Encryption&#xff0c;简称OE&#xff09;&#xff0c;作为一种新兴…

基于Qt的视频剪辑

在Qt中进行视频剪辑可以通过多种方式实现&#xff0c;但通常需要使用一些额外的库来处理视频数据。以下是一些常见的方法和步骤&#xff1a; 使用FFmpeg FFmpeg是一个非常强大的多媒体框架&#xff0c;可以用来处理视频和音频数据。你可以使用FFmpeg的命令行工具或者其库来实现…

skynet 入门篇

文章目录 概述1.实现了actor模型2.实现了服务器的基础组件 环境准备centosubuntumac编译安装 ActorActor模型定义组成 Actor调度工作线程流程工作线程权重工作线程执行规则 小结 概述 skynet 是一个轻量级服务器框架&#xff0c;而不仅仅用于游戏&#xff1b; 轻量级有以下几…

react18+

主要是围绕函数式组件讲&#xff0c;18主要用就是函数式组件&#xff0c;学习前先熟悉下原生js的基本使用&#xff0c;主要是事件 1、UI操作 1.1、书写jsx标签语言 基本写法和原生如同一则&#xff0c;只是放在一个方法里面返回而已&#xff0c;我们称这样的写法为函数式组件…

ChatTTS(文本转语音) 一键本地安装爆火语音模型

想不想让你喜欢的文章&#xff0c;有着一个动听的配音&#xff0c;没错&#xff0c;他就可以实现。 ChatTTS 是一款专为对话场景设计的文本转语音模型&#xff0c;例如 LLM 助手对话任务。它支持英语和中文两种语言。 当下爆火模型&#xff0c;在Git收获23.5k的Star&#xff…

面试重点---快速排序

快排单趟 快速排序是我们面试中的重点&#xff0c;这个知识点也很抽象&#xff0c;需要我们很好的掌握&#xff0c;而且快速排序的代码也是非常重要&#xff0c;需要我们懂了还不行&#xff0c;必须要手撕代码&#xff0c;学的透彻。 在研究快速排序之前&#xff0c;我们首先…

PyTorch 2.0 GPU Nvidia运行库的安装

【图书推荐】《PyTorch深度学习与计算机视觉实践》-CSDN博客 假设读者电脑带有NVIDIA 20 以上系列的显卡。 我们以CUDA 11.7cuDNN 8.2.0&#xff08;其他更高版本的组合&#xff0c;读者可以执行查阅PyTorch官网获得&#xff09;为例&#xff0c;讲解PyTorch 2.0 GPU版本的安…

【Linux】多线程4——线程同步/条件变量

1.Linux线程同步 1.1.同步概念与线程饥饿问题 先来理解同步的概念 什么是线程同步 在一般情况下&#xff0c;创建一个线程是不能提高程序的执行效率的&#xff0c;所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数&#xff0c;在多个线程同时对同一个内存地…

云服务器Ubuntu18.04进行Nginx配置

云服务器镜像版本信息&#xff1a;Ubuntu 18.04 server 64bit&#xff0c;本文记录了在改版本镜像上安装Nginx&#xff0c;并介绍了Nginx配置文件目录&#xff0c;便于后面再次有需求时进行复习。 文章目录 Nginx的安装Nginx配置文件分析 Nginx的安装 1.执行下面命令进行安装…

linux 部署flask项目

linux python环境安装: https://blog.csdn.net/weixin_41934979/article/details/140528410 1.创建虚拟环境 python3.12 -m venv .venv 2.激活环境 . .venv/bin/activate 3.安装依赖包(pip3.12 install -r requirements.txt) pip3.12 install -r requirements.txt 4.测试启…

使用git命令行的方式,将本地项目上传到远程仓库

在国内的开发环境中&#xff0c;git的使用是必不可少的。Git 是一款分布式版本控制系统&#xff0c;用于有效管理和追踪文件的变更历史及协作开发。本片文章就来介绍一下怎样使用git命令行的方式&#xff0c;将本地项目上传到远程仓库&#xff0c;虽然现在的IDE中基本都配置了g…

React类组件生命周期与this关键字

类组件生命周期 参考链接 一图胜千言&#xff08;不常用的生命周期函数已隐藏&#xff09; 代码&#xff1a; //CC1.js import { Component } from "react";export default class CC1 extends Component {constructor(props) {super(props);console.log("con…