C++ 拾遗

1. 从编码到执行的过程

预处理

删除注释

宏和inline

宏(Macro):
  • 预编译替换: 宏是在预处理阶段进行文本替换的,即在代码被编译之前。

  • 简单的字符串替换: 宏通常是简单的字符串替换,它将代码中的宏名称替换为宏定义的文本。

  • 类型检查: 宏的替换是简单的文本替换,不进行类型检查。这可能导致一些问题,因为宏不关心数据类型,可能引入潜在的错误。

  • 代码大小: 由于是文本替换,宏可能会生成较大的代码。每次使用宏时,都会复制宏的内容,可能导致代码膨胀。

  • 栈的使用: 宏不涉及函数调用,因此不需要调用栈。宏只是简单地进行文本替换。

inline 关键字:
  • 编译时替换: inline 是在编译时进行的,而不是在预处理阶段。编译器有权决定是否真正进行内联。

  • 类型检查: 内联函数会进行类型检查,因为内联函数在编译时被视为真正的函数调用。这有助于避免一些宏可能引入的类型问题。

  • 代码大小: 内联函数的代码通常比宏更小,因为它是在编译器控制下插入的。不像宏,不会导致代码膨胀。

  • 栈的使用: 内联函数与普通函数一样,可能涉及栈的使用。但是,由于内联函数通常较短,编译器可能会选择在调用点内联它,从而减少调用的开销。

处理预编译指令:include递归/条件编译

#pragma 编译器指令

#pragma 是一种用于向编译器发出特定指令的预处理器指令。它的作用是为了提供一种在不同编译器之间进行特定于平台或实现的设置和控制的标准化机制。#pragma 的具体行为因编译器而异,因为它们通常是编译器特定的。

编译:通过语法/词法分析生成汇编代码

汇编:将汇编代码转变成机器可以执行的指令

链接:

why?

目标文件主要分为两个区域:数据区域和指令区域。 每一个指令和数据都被安排了地址。

         (1)地址重定位: 目标文件被整合的时候,每个目标文件的数据区被整合到一起,每个目标文件的指令区被整合到一起。假如目标文件1被整合前指令的地址是00000001,目标文件n整合前指令的地址也是00000001,整合到一起后,他们的地址是要重新编排的,这个叫地址重定位。数据区域的地址相应的也要重新编排。

           重定位地址的作用:CPU会通过这个重定位的地址进行寻址,找到在内存中要执行的指令和数据,然后取出指令执行,并按照指令要求处理数据。重定位以后,会给执行文件中的计算机指令数据,重新安排地址,CPU会通过这些地址取指令执行,并处理这些数据。最终需要通过这些地址找到内存中的指令和数据。
 

        (2) 符号统一 :   直接举个C语言中的例子,假设程序有两个.c文件,分别是a.c和b.c,这两个文件中都有名叫var的变量,a.c被编译得到a.o,b.c被编译得到b.o,将a.o和b.o链接到同一个文件时,var命名重复了,需要根据规则对着两个符号进行统一,与此相似的还有函数名的“符号统一”问题。

连接器重复符号处理(一)动态库重复符号处理规则_程序和动态链接库定义了相同的符号-CSDN博客

静态

各自一个副本

快,更新难,占用空间多

函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。

空间浪费:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个  目标文件都有依赖,会出现同一个目标文件都在内存存在多个副本;

更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。

运行速度快:但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,  在执行的时候运行速度快。

动态

执行时共享,更新方便,不知道动态库地址

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。

共享库:就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副  本,而是这多个程序在执行时共享同一份副本;

更新方便:更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。

性能损耗:因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。

--> .so 文件放到 lib 下,每次在生产环境进行编译,

引申:不同语言的编译过程有什么区别?

c语言,java,Python跨平台运行原理_不同编程语言怎么联动-CSDN博客

c:编译后在不同操作系统是不同的机器码,所以不同操作系统编译c程序的编译器不一样

java:一次编译,不同平台运行,JVM把编译后的文件转换为JVM可以识别的字节码,再转变为不同平台可以识别的机器码

python:逐行解释器解释为机器码或者字节码。

机器码执行快,所以可以优化解释器。

JIT(Just-In-Time)编译器在运行时将热点代码编译为本地机器代码,以提高执行速度。

2.内存泄露

该释放的没释放

避免内存泄漏的补充建议:

  • 智能指针: 使用智能指针(std::shared_ptrstd::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)使用方法

  1. 智能指针重载了*和->操作符,可以像使用指针一样使用unique_ptr
  2. 不支持普通的拷贝和赋值。
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进行赋值。
  1. 不要用同一个裸指针初始化多个unique_ptr对象。
  2. get()方法返回裸指针。
  3. 不要用unique_ptr管理不是new分配的内存。

3)用于函数的参数

  1. 传引用(不能传值,因为unique_ptr没有拷贝构造函数)。
  1. 裸指针。

4)不支持指针的运算(+、-、++、--

二、更多技巧

1)将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。

unique_ptr<AA> p0;

p0 = unique_ptr<AA>(new AA ("西瓜"));

2)用nullptrunique_ptr赋值将释放对象,空的unique_ptr==nullptr

3release()释放对原始指针的控制权,将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);

7unique_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。

8unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放。

9unique_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:

  1. func1(pu.get());:这里使用 get 函数获取了 unique_ptr 的原始指针,但要注意,这样做并不会改变 unique_ptr 的所有权。函数 func1 需要一个指针,但不负责释放内存。这样使用是合法的。

  2. func2(pu.release());:使用 release 函数释放了 unique_ptr 对指针的所有权。这样做会导致原始的 unique_ptr 失去对内存的管理,你需要确保在合适的时候手动释放内存。在这里,func2 接收指针并负责释放内存。需要小心,release 后的 unique_ptr 不再拥有对内存的所有权。

  3. func3(pu);:这里将 unique_ptr 传递给 func3,但 func3 只是读取指针的内容,不会改变 unique_ptr 的所有权。这是合法的,因为 func3 不会负责释放内存。

  4. 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

  1. 共享所有权: 多个 shared_ptr 实例可以共享对同一块内存的所有权,即它们可以指向相同的对象。

  2. 引用计数: shared_ptr 内部维护一个引用计数,用来记录有多少个 shared_ptr 实例共享同一个对象。每次创建新的 shared_ptr,引用计数增加1,每次 shared_ptr 超出作用域,引用计数减1。

  3. 内存释放: 当引用计数减为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)使用方法

  1. 重载 *-> 操作符: 智能指针重载了这两个操作符,使得使用智能指针的时候可以像使用原始指针一样访问对象。

  2. use_count() 方法: 返回与该 shared_ptr 共享对象的所有 shared_ptr 实例的数量。这个数量也就是引用计数。

  3. unique() 方法: 判断引用计数是否为1,即判断是否是唯一的拥有者。如果是,返回 true;否则返回 false

  4. 赋值操作: shared_ptr 支持赋值操作。当一个 shared_ptr 被赋给另一个时,引用计数会相应地增加。赋值操作符确保了资源的正确管理。

  5. get() 方法: 返回指向托管对象的裸指针。这在需要传递原始指针的情况下很有用,但要小心不要在 shared_ptr 生命周期结束后继续使用裸指针。

  6. 避免多个 shared_ptr 管理同一个裸指针: 这是很重要的,因为它可能导致引用计数不正确,从而导致资源提前释放或者内存泄漏。

  7. 不要用 shared_ptr 管理不是 new 分配的内存: shared_ptr 的默认删除器是 delete,因此不要用 shared_ptr 管理不是通过 new 分配的内存,否则会导致未定义行为。

3)用于函数的参数

unique_ptr的原理相同。

4)不支持指针的运算(+、-、++、--

二、更多细节

  1. nullptrshared_ptr 赋值: 当将 nullptr 赋值给 shared_ptr 时,会减少引用计数。如果引用计数变为0,将释放关联的对象,最终空的 shared_ptr 会等于 nullptr

  2. std::move() 转移控制权: std::move() 可以将对原始指针的控制权转移到 shared_ptr 中。这也适用于将 unique_ptr 转移到 shared_ptr

  3. reset() 方法: reset() 方法改变了 shared_ptr 与资源的关联关系。可以解除与先前资源的关系,并将引用计数减1,也可以关联新的资源。

  4. swap() 方法: swap() 方法用于交换两个 shared_ptr 的控制权,这可以在需要时实现资源的交换。

  5. 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;
}
  1. shared_ptr 在程序退出时的注意事项: 在程序中调用 exit() 退出时,全局的 shared_ptr 可以自动释放,但是局部的 shared_ptr 无法释放。这是因为 exit() 函数终止程序时并不会调用局部对象的析构函数。

  2. shared_ptr 的数组版本: shared_ptr 提供了支持数组的具体化版本,重载了 [] 操作符,允许像处理数组一样使用。

  3. 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,资源不会被释放。

示例:

以上代码创建了两个类AABB,它们相互引用,即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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值