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

news2024/11/18 5:12:12

智能指针

  • 一、内存泄露
    • 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.coloradmin.cn/o/1955440.html

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

相关文章

敏感信息泄露wp

1.右键查看网页源代码 2.前台JS绕过&#xff0c;ctrlU绕过JS查看源码 3.开发者工具&#xff0c;网络&#xff0c;查看协议 4.后台地址在robots,拼接目录/robots.txt 5.用dirsearch扫描&#xff0c;看到index.phps,phps中有源码&#xff0c;拼接目录&#xff0c;下载index.phps …

【Go - context 速览,场景与用法】

作用 context字面意思上下文&#xff0c;用于关联管理上下文&#xff0c;具体有如下几个作用 取消信号传递&#xff1a;可以用来传递取消信号&#xff0c;让一个正在执行的函数知道它应该提前终止。超时控制&#xff1a;可以设定一个超时时间&#xff0c;自动取消超过执行时间…

Oat++ 后端实现跨域

这里记录在官方的例子中&#xff0c;加入跨域。Oat Example-CRUD 在官方的例子中&#xff0c;加入跨域。 Oat Example-CRUD 修改AppComponent.hpp文件中的代码&#xff0c;如下&#xff1a; #include "AppComponent.hpp"#include "controller/UserController…

8、从0搭建企业门户网站——网站部署

目录 正文 1、域名解析 2、云服务器端口授权 3、Mysql数据库初始化 4、上传网站软件包 5、Tomcat配置 6、运行Tomcat 7、停止Tomcat 8、部署后发现验证码无法使用 完毕! 正文 当云服务器租用、域名购买和软件开发都完成后,我们就可以开始网站部署上线,ICP备案会长…

表达式的转换

题目&#xff1a; 表达式的转换 - 洛谷 P1175 - Virtual Judge 思路&#xff1a; 这道题可以拆成两问&#xff1a; 第一问&#xff0c;将中缀表达式转成后缀表达式放入一个数组 第二问&#xff0c;将后缀表达式的数组计算&#xff0c;并输出过程 第一问思路&#xff1a; …

Python高维度大型气象矩阵存储策略分享

零、前情提要 最近需要分析全球范围多变量的数值预报数据&#xff0c;将grb格式的数据下载下来经过一通处理后需要将预处理数据先保存一遍&#xff0c;方便后续操作&#xff0c;处理完发现此时的数据维度很多&#xff0c;数据量巨大&#xff0c;使用不同的保存策略的解析难度和…

信息安全技术解析

在信息爆炸的今天&#xff0c;信息技术安全已成为社会发展的重要基石。随着网络攻击的日益复杂和隐蔽&#xff0c;保障数据安全、提升防御能力成为信息技术安全领域的核心任务。本文将从加密解密技术、安全行为分析技术和网络安全态势感知技术三个方面进行深入探讨&#xff0c;…

C++笔试强训9

文章目录 一、选择题1-5题6-10题 二、编程题题目一题目二 一、选择题 1-5题 函数形参是个int类型的引用&#xff0c;传参的时候直接传一个int类型的变量就行 故选A 1.malloc/calloc/realloc—>free 2. new / delete 3. new[] / delete[] 一定要匹配起来使用&#xff0c;否则…

猫头虎分享:Numpy知识点一文带你详细学习np.random.randn()

&#x1f42f; 猫头虎分享&#xff1a;Numpy知识点一文带你详细学习np.random.randn() 摘要 Numpy 是数据科学和机器学习领域中不可或缺的工具。在本篇文章中&#xff0c;我们将深入探讨 np.random.randn()&#xff0c;一个用于生成标准正态分布的强大函数。通过详细的代码示…

ADS 使用教程(二十九)Understanding Bounding Area Layer for FEM

上一篇&#xff1a;ADS 使用教程&#xff08;二十八&#xff09;Working with FEM Mesh & Field Data in ADS 这一节&#xff0c;我们来一起了解一下有限元法&#xff08;FEM&#xff09;中的边界区域层&#xff08;Bounding Area Layer&#xff09;&#xff0c;这是定义仿…

python项目实例和源码权限管理系统

✌网站介绍&#xff1a;✌10年项目辅导经验、专注于计算机技术领域学生项目实战辅导。 ✌服务范围&#xff1a;Java(SpringBoo/SSM)、Python、PHP、Nodejs、爬虫、数据可视化、小程序、安卓app、大数据等设计与开发。 ✌服务内容&#xff1a;免费功能设计、免费提供开题答辩P…

浅谈HOST,DNS与CDN

首先这个是网络安全的基础&#xff0c;需得牢牢掌握。 1.什么是HOST HOSTS文件&#xff1a; 定义&#xff1a; HOSTS文件是一个操作系统级别的文本文件&#xff0c;通常位于操作系统的系统目录中&#xff08;如Windows系统下的C:\Windows\System32\drivers\etc\hosts&#xf…

黑马头条vue2.0项目实战(二)——登录注册功能的实现

1. 布局结构 目标 能实现登录页面的布局 能实现基本登录功能 能掌握 Vant 中 Toast 提示组件的使用 能理解 API 请求模块的封装 能理解发送验证码的实现思路 能理解 Vant Form 实现表单验证的使用 这里主要使用到三个 Vant 组件&#xff1a; NavBar 导航栏 Form 表单 F…

windows 安装 Linux 子系统 Ubuntu,并编译安装nginx

1. 安装Ubuntu 首先可以在 Microsoft Store 自行搜索安装 Ubuntu&#xff0c;个人建议安装 22 版本的即可。Ubuntu安装完成后&#xff0c;以管理员身份打开CMD&#xff0c;运行如下命令&#xff1a; wsl --install 此时打开Ubuntu已经可以正常使用了。 2. 安装C/C编译器 对于…

动态规划专题:线性dp、背包问题,区间

目录 方块与收纳盒 舔狗舔到最后一无所有 可爱の星空 数字三角形 花店橱窗 [NOI1998]免费馅饼 [NOIP2002]过河卒 [NOIP2008]传球游戏 「木」迷雾森林 [NOIP2004]合唱队形 [NOIP1999]拦截导弹 数学考试 小A买彩票 购物 牛牛的旅游纪念品 [NOIP2001]装箱问题 [N…

网络轮询器 NetPoller

网络轮询器 NetPoller 网络轮询器是 Go 语言运行时用来处理 I/O 操作的关键组件&#xff0c;它使用了操作系统提供的 I/O 多路复用机制增强程序的并发处理能力。网络轮询器不仅用于监控网络 I/O&#xff0c;还能用于监控文件的 I/O&#xff0c;它利用了操作系统提供的 I/O 多路…

How can I fix my Flask server‘s 405 error that includes OpenAi api?

题意&#xff1a;解决包含OpenAI API的Flask服务器中出现的405错误&#xff08;Method Not Allowed&#xff0c;即方法不允许&#xff09; 问题背景&#xff1a; Im trying to add an API to my webpage and have never used any Flask server before, I have never used Java…

MATLAB进阶:函数和方程

经过前几天的学习&#xff0c;matlab基础我们已经大致了解&#xff0c;现在我们继续学习matlab更进一步的应用。 常用函数 在求解有关多项式的计算时&#xff0c;我们无可避免的会遇到以下几个函数 ypolyval(p,x)&#xff1a;求得多项式p在x处的值y&#xff0c;x可以是一个或…

ComfyUI反推提示词节点报错:Load model failed

&#x1f3a0;报错现象 反推提示词的时候会提示报错&#xff1a; Error occurred when executing WD14Tagger|pysssss: [ONNXRuntimeError] : 3 : NO_SUCHFILE : Load model from F:\ComfyUI-aki\custom_nodes\ComfyUI-WD14-Tagger\models\wd-v1-4-convnext-tagger-v2.onnx fa…

创建mysql库,及webserver使用编译

首先安装mysql sudo apt update sudo apt install mysql-server sudo systemctl status mysql #检查mysql是否安装成功 sudo mysql #进入mysqlSHOW DATABASES; create database yourdb; #创建一个名为yourdb的数据库USE yourdb; #使用刚才创建好的数据库 CREATE TABLE …