C++ 拾遗
1. 从编码到执行的过程
预处理
删除注释
宏和inline
宏(Macro):
-
预编译替换: 宏是在预处理阶段进行文本替换的,即在代码被编译之前。
-
简单的字符串替换: 宏通常是简单的字符串替换,它将代码中的宏名称替换为宏定义的文本。
-
类型检查: 宏的替换是简单的文本替换,不进行类型检查。这可能导致一些问题,因为宏不关心数据类型,可能引入潜在的错误。
-
代码大小: 由于是文本替换,宏可能会生成较大的代码。每次使用宏时,都会复制宏的内容,可能导致代码膨胀。
-
栈的使用: 宏不涉及函数调用,因此不需要调用栈。宏只是简单地进行文本替换。
inline
关键字:
-
编译时替换:
inline
是在编译时进行的,而不是在预处理阶段。编译器有权决定是否真正进行内联。 -
类型检查: 内联函数会进行类型检查,因为内联函数在编译时被视为真正的函数调用。这有助于避免一些宏可能引入的类型问题。
-
代码大小: 内联函数的代码通常比宏更小,因为它是在编译器控制下插入的。不像宏,不会导致代码膨胀。
-
栈的使用: 内联函数与普通函数一样,可能涉及栈的使用。但是,由于内联函数通常较短,编译器可能会选择在调用点内联它,从而减少调用的开销。
处理预编译指令:include递归/条件编译
#pragma 编译器指令
#pragma
是一种用于向编译器发出特定指令的预处理器指令。它的作用是为了提供一种在不同编译器之间进行特定于平台或实现的设置和控制的标准化机制。#pragma
的具体行为因编译器而异,因为它们通常是编译器特定的。
编译:通过语法/词法分析生成汇编代码
汇编:将汇编代码转变成机器可以执行的指令
链接:
静态
各自一个副本
快,更新难,占用空间多
动态
执行时共享,更新方便
2.内存泄露
该释放的没释放
避免内存泄漏的补充建议:
-
智能指针: 使用智能指针(
std::shared_ptr
和std::unique_ptr
)可以大大简化内存管理。它们使用 RAII(资源获取即初始化)原则,确保在离开作用域时自动释放内存。 -
RAII(资源获取即初始化): 不仅仅是智能指针,任何资源(包括文件句柄、网络连接等)都可以使用 RAII 进行管理。通过对象的生命周期管理资源的分配和释放,减少手动管理的错误。
-
计数法
-
基类的析构函数为虚函数
-
编码规范和代码审查: 强调良好的编码规范,进行代码审查时特别注意内存管理的正确性。遵循一致的规范和最佳实践可以降低出现内存泄漏的可能性。
-
内存分析工具: 除了检测工具外,一些集成开发环境(IDE)也提供了内存分析工具,例如 Visual Studio 的 Memory Debugger。
3.智能指针 unique_ptr
是的,你对 unique_ptr
的特性描述是正确的。unique_ptr
是一种智能指针,它确保在任意时刻只有一个 unique_ptr
拥有对其指向的对象的所有权。当 unique_ptr
被销毁时,它所管理的对象也会被销毁。
这种独占所有权的特性使得 unique_ptr
适合在需要管理动态分配的资源(如堆上的对象)时使用,因为它确保了资源的所有权不会被多个指针共享,从而避免了潜在的资源管理问题。
不同方式调用构造函数
SomeType* rawPtr = new SomeType();
unique_ptr<SomeType> ptr = unique_ptr<SomeType>(rawPtr); // 显式调用构造函数
SomeType* rawPtr = new SomeType();
unique_ptr<SomeType> ptr = rawPtr; // 错误,禁止隐式转换
包含头文件:
???????#include <memory>
template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
explicit unique_ptr(pointer p) noexcept; // 不可用于转换函数。
~unique_ptr() noexcept; ???
T& operator*() const; ???????????// 重载*操作符。
T* operator->() const noexcept; ?// 重载->操作符。
unique_ptr(const unique_ptr &) = delete; ??// 禁用拷贝构造函数。
unique_ptr& operator=(const unique_ptr &) = delete; ?// 禁用赋值函数。
unique_ptr(unique_ptr &&) noexcept; ??// 右值引用。
unique_ptr& operator=(unique_ptr &&) noexcept; ?// 右值引用。
// ...
private:
pointer ptr; ?// 内置的指针。
};
第一个模板参数T:指针指向的数据类型。
第二个模板参数D:指定删除器,缺省用delete释放资源。
测试类AA的定义:
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
~AA() { cout << m_name << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
一、基本用法
1)初始化
方法一:
unique_ptr<AA> p0(new AA("西施")); ????// 分配内存并初始化。
方法二:???????
unique_ptr<AA> p0 = make_unique<AA>("西施"); ??// C++14标准。
unique_ptr<int> pp1=make_unique<int>(); ????????// 数据类型为int。
unique_ptr<AA> pp2 = make_unique<AA>(); ??????// 数据类型为AA,默认构造函数。
unique_ptr<AA> pp3 = make_unique<AA>("西施"); ?// 数据类型为AA,一个参数的构造函数。
unique_ptr<AA> pp4 = make_unique<AA>("西施",8); // 数据类型为AA,两个参数的构造函数。
2)使用方法
- 智能指针重载了*和->操作符,可以像使用指针一样使用unique_ptr。
- 不支持普通的拷贝和赋值。
AA* p = new AA("西施");
unique_ptr<AA> pu2 = p; ?????????????// 错误,不能把普通指针直接赋给智能指针。
unique_ptr<AA> pu3 = new AA("西施"); // 错误,不能把普通指针直接赋给智能指针。
unique_ptr<AA> pu2 = pu1; ??????????// 错误,不能用其它unique_ptr拷贝构造。
unique_ptr<AA> pu3;
pu3 = pu1; ???????????????????????????// 错误,不能用=对unique_ptr进行赋值。
- 不要用同一个裸指针初始化多个unique_ptr对象。
- get()方法返回裸指针。
- 不要用unique_ptr管理不是new分配的内存。
3)用于函数的参数
- 传引用(不能传值,因为unique_ptr没有拷贝构造函数)。
- 裸指针。
4)不支持指针的运算(+、-、++、--)
二、更多技巧
1)将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。
unique_ptr<AA> p0;
p0 = unique_ptr<AA>(new AA ("西瓜"));
2)用nullptr给unique_ptr赋值将释放对象,空的unique_ptr==nullptr。
3)release()释放对原始指针的控制权,将unique_ptr置为空,返回裸指针。(可用于把unique_ptr传递给子函数,子函数将负责释放对象)
4)std::move()可以转移对原始指针的控制权。(可用于把unique_ptr传递给子函数,子函数形参也是unique_ptr)
5)reset()释放对象。
void reset(T * _ptr= (T *) nullptr);
pp.reset(); ???????// 释放pp对象指向的资源对象。
pp.reset(nullptr); ?// 释放pp对象指向的资源对象
pp.reset(new AA("bbb")); ?// 释放pp指向的资源对象,同时指向新的对象。
6)swap()交换两个unique_ptr的控制权。
void swap(unique_ptr<T> &_Right);
7)unique_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。
8)unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放。
9)unique_ptr提供了支持数组的具体化版本。
数组版本的unique_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。
// unique_ptr<int[]> parr1(new int[3]); ?????????// 不指定初始值。
unique_ptr<int[]> parr1(new int[3]{ 33,22,11 }); ?// 指定初始值。
cout << "parr1[0]=" << parr1[0] << endl;
cout << "parr1[1]=" << parr1[1] << endl;
cout << "parr1[2]=" << parr1[2] << endl;
unique_ptr<AA[]> parr2(new AA[3]{string("西施"), string("冰冰"), string("幂幂")});
cout << "parr2[0].m_name=" << parr2[0].m_name << endl;
cout << "parr2[1].m_name=" << parr2[1].m_name << endl;
cout << "parr2[2].m_name=" << parr2[2].m_name << endl;
示例1:
-
func1(pu.get());
:这里使用get
函数获取了unique_ptr
的原始指针,但要注意,这样做并不会改变unique_ptr
的所有权。函数func1
需要一个指针,但不负责释放内存。这样使用是合法的。 -
func2(pu.release());
:使用release
函数释放了unique_ptr
对指针的所有权。这样做会导致原始的unique_ptr
失去对内存的管理,你需要确保在合适的时候手动释放内存。在这里,func2
接收指针并负责释放内存。需要小心,release
后的unique_ptr
不再拥有对内存的所有权。 -
func3(pu);
:这里将unique_ptr
传递给func3
,但func3
只是读取指针的内容,不会改变unique_ptr
的所有权。这是合法的,因为func3
不会负责释放内存。 -
func4(move(pu));
:使用move
转移了unique_ptr
的所有权。现在pu
不再拥有对内存的所有权,而是被传递给了func4
。在func4
内,unique_ptr
被接受并继续拥有对内存的所有权。
#include <iostream>
#include <memory>
using ?namespace std;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
// 函数func1()需要一个指针,但不对这个指针负责。
void func1(const AA* a) {
cout << a->m_name << endl;
}
// 函数func2()需要一个指针,并且会对这个指针负责。
void func2(AA* a) {
cout << a->m_name << endl;
delete a;
}
// 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。
void func3(const unique_ptr<AA> &a) {
cout << a->m_name << endl;
}
// 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。
void func4(unique_ptr<AA> a) {
cout << a->m_name << endl;
}
int main()
{
unique_ptr<AA> pu(new AA("西施"));
cout << "开始调用函数。\n";
//func1(pu.get()); ???????// 函数func1()需要一个指针,但不对这个指针负责。
//func2(pu.release()); ?// 函数func2()需要一个指针,并且会对这个指针负责。
//func3(pu); ???????????????// 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。
func4(move(pu)); ????// 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。
cout << "调用函数完成。\n";
if (pu == nullptr) cout << "pu是空指针。\n";
}
示例2:???????
#include <iostream>
#include <memory>
using ?namespace std;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
int main()
{
//AA* parr1 = new AA[2]; ??// 普通指针数组。
AA* parr1 = new AA[2]{ string("西施"), string("冰冰") };
//parr1[0].m_name = "西施1";
//cout << "parr1[0].m_name=" << parr1[0].m_name << endl;
//parr1[1].m_name = "西施2";
//cout << "parr1[1].m_name=" << parr1[1].m_name << endl;
//delete [] parr1;
unique_ptr<AA[]> parr2(new AA[2]); ??// unique_ptr数组。
//unique_ptr<AA[]> parr2(new AA[2]{ string("西施"), string("冰冰") });
parr2[0].m_name = "西施1";
cout << "parr2[0].m_name=" << parr2[0].m_name << endl;
parr2[1].m_name = "西施2";
cout << "parr2[1].m_name=" << parr2[1].m_name << endl;
}
这部分使用了 unique_ptr
创建了一个 AA
类型的数组。不同于普通指针数组,unique_ptr
会在其生命周期结束时自动释放内存,不需要手动调用 delete []
。在这里,unique_ptr<AA[]>
代表一个动态数组,通过 parr2[0]
和 parr2[1]
访问数组元素,而不需要手动管理内存。
4.智能指针?shared_ptr
-
共享所有权: 多个
shared_ptr
实例可以共享对同一块内存的所有权,即它们可以指向相同的对象。 -
引用计数:
shared_ptr
内部维护一个引用计数,用来记录有多少个shared_ptr
实例共享同一个对象。每次创建新的shared_ptr
,引用计数增加1,每次shared_ptr
超出作用域,引用计数减1。 -
内存释放: 当引用计数减为0时,表示没有任何
shared_ptr
指向该对象,此时释放相关的内存。这保证了动态分配的对象在不再被引用时被正确释放,避免了内存泄漏。
一、基本用法
shared_ptr的构造函数也是explicit,但是,没有删除拷贝构造函数和赋值函数。
1)初始化
方法一:
shared_ptr<AA> p0(new AA("西施")); ????// 分配内存并初始化。
方法二:
shared_ptr<AA> p0 = make_shared<AA>("西施"); ?// C++11标准,效率更高。
shared_ptr<int> pp1=make_shared<int>(); ????????// 数据类型为int。
shared_ptr<AA> pp2 = make_shared<AA>(); ??????// 数据类型为AA,默认构造函数。
shared_ptr<AA> pp3 = make_shared<AA>("西施"); ?// 数据类型为AA,一个参数的构造函数。
shared_ptr<AA> pp4 = make_shared<AA>("西施",8); // 数据类型为AA,两个参数的构造函数。
方法三:
AA* p = new AA("西施");
shared_ptr<AA> p0(p); ?????????????????// 用已存在的地址初始化。
方法四:
shared_ptr<AA> p0(new AA("西施"));
shared_ptr<AA> p1(p0); ????????????????// 用已存在的shared_ptr初始化,计数加1。
shared_ptr<AA> p1=p0; ????????????????// 用已存在的shared_ptr初始化,计数加1。
2)使用方法
-
重载
*
和->
操作符: 智能指针重载了这两个操作符,使得使用智能指针的时候可以像使用原始指针一样访问对象。 -
use_count()
方法: 返回与该shared_ptr
共享对象的所有shared_ptr
实例的数量。这个数量也就是引用计数。 -
unique()
方法: 判断引用计数是否为1,即判断是否是唯一的拥有者。如果是,返回true
;否则返回false
。 -
赋值操作:
shared_ptr
支持赋值操作。当一个shared_ptr
被赋给另一个时,引用计数会相应地增加。赋值操作符确保了资源的正确管理。 -
get()
方法: 返回指向托管对象的裸指针。这在需要传递原始指针的情况下很有用,但要小心不要在shared_ptr
生命周期结束后继续使用裸指针。 -
避免多个
shared_ptr
管理同一个裸指针: 这是很重要的,因为它可能导致引用计数不正确,从而导致资源提前释放或者内存泄漏。 -
不要用
shared_ptr
管理不是new
分配的内存:shared_ptr
的默认删除器是delete
,因此不要用shared_ptr
管理不是通过new
分配的内存,否则会导致未定义行为。
3)用于函数的参数
与unique_ptr的原理相同。
4)不支持指针的运算(+、-、++、--)
二、更多细节
-
用
nullptr
给shared_ptr
赋值: 当将nullptr
赋值给shared_ptr
时,会减少引用计数。如果引用计数变为0,将释放关联的对象,最终空的shared_ptr
会等于nullptr
。 -
std::move()
转移控制权:std::move()
可以将对原始指针的控制权转移到shared_ptr
中。这也适用于将unique_ptr
转移到shared_ptr
。 -
reset()
方法:reset()
方法改变了shared_ptr
与资源的关联关系。可以解除与先前资源的关系,并将引用计数减1,也可以关联新的资源。 -
swap()
方法:swap()
方法用于交换两个shared_ptr
的控制权,这可以在需要时实现资源的交换。 -
shared_ptr
的多态性:shared_ptr
在管理基类对象和派生类对象时也具有多态性。这意味着它可以正确地调用虚函数,实现多态行为。
#include <iostream>
#include <memory>
class Shape {
public:
virtual void draw() const {
std::cout << "Drawing a generic shape\n";
}
virtual ~Shape() {
std::cout << "Destroying a shape\n";
}
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle\n";
}
~Circle() override {
std::cout << "Destroying a circle\n";
}
};
class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a rectangle\n";
}
~Rectangle() override {
std::cout << "Destroying a rectangle\n";
}
};
int main() {
// 使用 shared_ptr 管理基类对象
std::shared_ptr<Shape> shapePtr;
// 创建一个 Circle 对象,并将其交给 shared_ptr 管理
shapePtr = std::make_shared<Circle>();
shapePtr->draw(); // 正确地调用了 Circle 类的 draw() 函数
// 创建一个 Rectangle 对象,并将其交给 shared_ptr 管理
shapePtr = std::make_shared<Rectangle>();
shapePtr->draw(); // 正确地调用了 Rectangle 类的 draw() 函数
// 其他操作...
// shared_ptr 管理的对象在 main 函数结束时会被自动销毁,调用相应的析构函数
return 0;
}
-
shared_ptr
在程序退出时的注意事项: 在程序中调用exit()
退出时,全局的shared_ptr
可以自动释放,但是局部的shared_ptr
无法释放。这是因为exit()
函数终止程序时并不会调用局部对象的析构函数。 -
shared_ptr
的数组版本:shared_ptr
提供了支持数组的具体化版本,重载了[]
操作符,允许像处理数组一样使用。 -
shared_ptr
的线程安全性: 引用计数的操作是原子的,因此shared_ptr
的引用计数本身是线程安全的。多个线程同时读同一个shared_ptr
对象是线程安全的。对于多线程读写同一个shared_ptr
对象,需要额外的锁来保护。
11)如果unique_ptr能解决问题,就不要用shared_ptr。unique_ptr的效率更高,占用的资源更少。
示例1:
#include <iostream>
#include <memory>
using ?namespace std;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
int main()
{
shared_ptr<AA> pa0(new AA("西施a")); ????// 初始化资源西施a。
shared_ptr<AA> pa1 = pa0; ??????????????????????// 用已存在的shared_ptr拷贝构造,计数加1。
shared_ptr<AA> pa2 = pa0; ??????????????????????// 用已存在的shared_ptr拷贝构造,计数加1。
cout << "pa0.use_count()=" << pa0.use_count() << endl; ??// 值为3。
shared_ptr<AA> pb0(new AA("西施b")); ???// 初始化资源西施b。
shared_ptr<AA> pb1 = pb0; ?????????????????????// 用已存在的shared_ptr拷贝构造,计数加1。
cout << "pb0.use_count()=" << pb0.use_count() << endl; ??// 值为2。
pb1 = pa1; ?????// 资源西施a的引用加1,资源西施b的引用减1。
pb0 = pa1; ?????// 资源西施a的引用加1,资源西施b的引用成了0,将被释放。
cout << "pa0.use_count()=" << pa0.use_count() << endl; ??// 值为5。
cout << "pb0.use_count()=" << pb0.use_count() << endl; ??// 值为5。
}
5.智能指针的删除器
在默认情况下,智能指针过期的时候,用delete原始指针;?释放它管理的资源。
程序员可以自定义删除器,改变智能指针释放资源的行为。
删除器可以是全局函数、仿函数和Lambda表达式,形参为原始指针。
示例:
#include <iostream>
#include <memory>
using namespace std;
// 定义一个简单的类AA
class AA
{
public:
string m_name;
// 构造函数,输出对象创建信息
AA() { cout << m_name << "调用构造函数AA()。\n"; }
// 带参数的构造函数,输出对象创建信息
AA(const string &name) : m_name(name) { cout << "调用构造函数AA(" << m_name << ")。\n"; }
// 析构函数,输出对象销毁信息
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
// 删除器,普通函数
void deletefunc(AA *a)
{
cout << "自定义删除器(全局函数)。\n";
delete a;
}
// 删除器,仿函数
struct deleteclass
{
// 仿函数的函数调用运算符,用于释放资源
void operator()(AA *a)
{
cout << "自定义删除器(仿函数)。\n";
delete a;
}
};
// 删除器,Lambda表达式
auto deleterlamb = [](AA *a)
{
cout << "自定义删除器(Lambda)。\n";
delete a;
};
int main()
{
// 使用shared_ptr和不同的删除器创建对象
shared_ptr<AA> pa1(new AA("西施a"), deletefunc);
//shared_ptr<AA> pa2(new AA("西施b"), deleteclass());
//shared_ptr<AA> pa3(new AA("西施c"), deleterlamb);
// 使用unique_ptr和不同的删除器创建对象
//unique_ptr<AA, decltype(deletefunc) *> pu1(new AA("西施1"), deletefunc);
// unique_ptr<AA, void (*)(AA*)> pu0(new AA("西施1"), deletefunc);
//unique_ptr<AA, deleteclass> pu2(new AA("西施2"), deleteclass());
//unique_ptr<AA, decltype(deleterlamb)> pu3(new AA("西施3"), deleterlamb);
}
6.智能指针weak_ptr
一、shared_ptr存在的问题
shared_ptr内部维护了一个共享的引用计数器,多个shared_ptr可以指向同一个资源。
如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放。
示例:
以上代码创建了两个类AA
和BB
,它们相互引用,即AA
类中有一个指向BB
类的shared_ptr
,而BB
类中也有一个指向AA
类的shared_ptr
。这种相互引用的结构也被称为循环引用。
#include <iostream>
#include <memory>
using ?namespace std;
class BB;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
shared_ptr<BB> m_p;
};
class BB
{
public:
string m_name;
BB() { cout << m_name << "调用构造函数BB()。\n"; }
BB(const string& name) : m_name(name) { cout << "调用构造函数BB(" << m_name << ")。\n"; }
~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
shared_ptr<AA> m_p;
};
int main()
{
shared_ptr<AA> pa = make_shared<AA>("西施a");
shared_ptr<BB> pb = make_shared<BB>("西施b");
pa->?m_p = pb;
pb->m_p = pa;
}
二、weak_ptr是什么
weak_ptr
的设计初衷就是为了解决循环引用导致的内存泄漏问题,而不影响被管理对象的生命周期。它不会增加引用计数,只是观测shared_ptr
的生命周期,当最后一个shared_ptr
销毁时,即使有相关的weak_ptr
存在,资源也会被正确释放。
weak_ptr
通常用于解决循环引用的问题,其中两个或多个对象相互引用,但又希望在某一时刻能够释放它们之间的引用关系。通过将其中一个对象的成员使用weak_ptr
来引用另一个对象,可以避免形成循环引用,确保在需要时正确释放资源。
感谢你的补充,确实强调了weak_ptr
的角色是作为shared_ptr
的辅助。
示例:
#include <iostream>
#include <memory>
using ?namespace std;
class BB;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
weak_ptr<BB> m_p;
};
class BB
{
public:
string m_name;
BB() { cout << m_name << "调用构造函数BB()。\n"; }
BB(const string& name) : m_name(name) { cout << "调用构造函数BB(" << m_name << ")。\n"; }
~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
weak_ptr<AA> m_p;
};
int main()
{
shared_ptr<AA> pa = make_shared<AA>("西施a");
shared_ptr<BB> pb = make_shared<BB>("西施b");
cout << "pa.use_count()=" << pa.use_count() << endl;
cout << "pb.use_count()=" << pb.use_count() << endl;
pa->m_p = pb;
pb->m_p = pa;
cout << "pa.use_count()=" << pa.use_count() << endl;
cout << "pb.use_count()=" << pb.use_count() << endl;
}
三、如何使用weak_ptr
weak_ptr没有重载 ->和 *操作符,不能直接访问资源。
有以下成员函数:
1)operator=();??// 把shared_ptr或weak_ptr赋值给weak_ptr。
2)expired(); ????// 判断它指资源是否已过期(已经被销毁)。
3)lock(); ???????// 返回shared_ptr,如果资源已过期,返回空的shared_ptr。
4)reset();???????// 将当前weak_ptr指针置为空。
5)swap(); ??????// 交换。
weak_ptr
的主要作用之一就是通过lock()
函数提升为shared_ptr
,以确保在访问资源时不会出现悬空指针的情况。
lock()
函数的行为是线程安全的,这使得在多线程环境中使用weak_ptr
更加安全。当lock()
被调用时,它会检查关联的shared_ptr
是否仍然有效,如果是,它就返回一个指向相同资源的有效的shared_ptr
;如果不是,它返回一个空的shared_ptr
。
这种机制可以很好地配合shared_ptr
,在不影响对象生命周期的前提下,通过weak_ptr
来进行观测,确保在访问资源时有一个有效的shared_ptr
。
示例:
#include <iostream>
#include <memory>
using ?namespace std;
class BB;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数AA()。\n"; }
AA(const string& name) : m_name(name) { cout << "调用构造函数AA(" << m_name << ")。\n"; }
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
weak_ptr<BB> m_p;
};
class BB
{
public:
string m_name;
BB() { cout << m_name << "调用构造函数BB()。\n"; }
BB(const string& name) : m_name(name) { cout << "调用构造函数BB(" << m_name << ")。\n"; }
~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
weak_ptr<AA> m_p;
};
int main()
{
shared_ptr<AA> pa = make_shared<AA>("西施a");
{
shared_ptr<BB> pb = make_shared<BB>("西施b");
pa->m_p = pb;
pb->m_p = pa;
shared_ptr<BB> pp = pa->m_p.lock(); ???????????// 把weak_ptr提升为shared_ptr。
if (pp == nullptr)
cout << "语句块内部:pa->m_p已过期。\n";
else
cout << "语句块内部:pp->m_name=" << pp->m_name << endl;
}
shared_ptr<BB> pp = pa->m_p.lock(); ???????????// 把weak_ptr提升为shared_ptr。
if (pp == nullptr)
cout << "语句块外部:pa->m_p已过期。\n";
else
cout << "语句块外部:pp->m_name=" << pp->m_name << endl;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!