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


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

一、内存泄露

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愿天堂没有C++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值