当前位置:   article > 正文

C++智能指针使用详解

c++智能指针使用

概述

在开发C++代码的时候内存管理是一个很麻烦的事情,开发人员一般都是遵守谁申请谁释放的原则,但是实际开发过程中难免有疏忽。当存在多个模块互相传递数据时更是困难,因为不知道数据什么时候会用完,因此也很难释放从而导致内存泄漏。

智能指针是一种C++对象,它模拟传统的原始指针的行为,但可以自动管理所指向的内存。智能指针的原理就是维护一个引用计数,指向相同内存的智能指针之间赋值或者拷贝都会让内部的引用计数增加,出了某些作用域时引用计数减少,当引用计数等于0时指针自动删除内存对象。

智能指针内存示意图

标准库中的智能指针

创建shared_ptr

int main(int argc, char *argv[]) {
    // ......
    // 常用声明方式
    std::shared_ptr<Student> student_ptr_1 = std::shared_ptr<Student>(new Student());
    // 推荐使用方式
    std::shared_ptr<Student> student_ptr_2 = std::make_shared<Student>();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

创建weak_ptr

weak_ptr和shared_ptr这两指针类型最好配套使用,weak_ptr更像是一个shared_ptr观察器,检测智能指针是否有效的一个观察器。weak_ptr通过shared_ptr的赋值创建,而且不会引起计数器的改变。

int main(int argc, char *argv[]) {
    // ......

    // 赋值给弱引用
    std::weak_ptr<Student> student_ptr_3 = student_ptr_2;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用智能指针

shared_ptr

成员函数
  • use_count()

统计指向同一内存对象的智能指针的个数。

  • swap()

交换两个智能指针所指向的内存地址。

  • reset()

当前智能指针放弃内存的所有权,引起计数器减少,同时,清空智能指针,是智能指针变为empty状态。

  • unique()

判断当前智能指针变量是否是唯一所有权(use_count() = 1)。

  • get()

获取当前智能指针包含的实际类型对象地址。

示例演示
  • 引用计数的增加和减少
int main(int argc, char *argv[]) {
    // ......
    std::shared_ptr<Student> student_ptr = std::make_shared<Student>();
    qInfo() << "[1] 引用次数:" << student_ptr.use_count();
    std::shared_ptr<Student> student_copy_ptr = student_ptr;
    qInfo() << "[2] 引用次数:" << student_ptr.use_count();
    {
        // 局部作用域
        std::shared_ptr<Student> student_scope_ptr = student_ptr;
        qInfo() << "[3] 引用次数:" << student_ptr.use_count();
    }
    qInfo() << "[4] 引用次数:" << student_ptr.use_count();
}
/*
[1] 引用次数: 1
[2] 引用次数: 2
[3] 引用次数: 3
[4] 引用次数: 2
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

从上面这个代码中可以看出,基于原始的对象进行多次引用后其引用的次数确实是在增加,其中有一次引用是在一个小作用域内,此时当离开这个作用域再次查看其应用次数时计数减少。

  • 对象的析构和释放
int main(int argc, char *argv[]) {
    // ......
    {
        // 构造代带参数的对象
        std::shared_ptr<Student> student_ptr = std::make_shared<Student>(1);
        qInfo() << "[1] 引用次数:" << student_ptr.use_count();
    }
}
/*
[1] 引用次数: 1
id 1 析构了
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在第一个示例中知道,在作用域范围内创建的智能指针,在离开作用域范围后就会造成计数器减少,第二个示例则说明当计数器变为0之后,智能指针就会释放掉该对象内存。

  • 获取管理的对象
int main(int argc, char *argv[]) {
    // ......
    {
        {
            std::shared_ptr<Student> student_ptr = std::make_shared<Student>(2);
            // 获取实际的 Student 对象
            Student* student = dynamic_cast<Student*>(student_ptr.get());
            qInfo() << "学员 ID : " << student->getId();
            // 直接放弃管理权造成析构
            student_ptr.reset();
            qInfo() << "退出第一层作用域.";
        }
        qInfo() << "退出第二层作用域.";
    }
}
/*
学员 ID :  2
id 2 析构了
退出第一层作用域
退出第二层作用域
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

使用get()函数可以获取其管理的实际对象,但需要使用dynamic_cast进行类型的转换。此外,使用reset()函数可以放弃堆内存的管理权限,如果放弃时只剩最后一次引用则释放内存。

weak_ptr

成员函数
  • lock()

创建一个shared_ptr对象,该对象与原始的shared_ptr对象共享管理的内存。

  • expired()

该函数可以检测指向的真实地址是否有效。

  • release()

放弃对真实指针地址的控制权,并返回真实指针地址,返回的地址可以使用delete进行内存释放。

示例演示
  • 判断管理的内存是否有效
int main(int argc, char *argv[]) {
    // ......
    std::weak_ptr<Student> watcher;
    // 创建一个若引用对象
    std::shared_ptr<Student> student_ptr =  std::make_shared<Student>(2);
    watcher = student_ptr;

    qInfo() << "[1] 引用次数:" << watcher.use_count();
    if (!watcher.expired()) {
        qInfo() << "内存有效";
    } else {
        qInfo() << "内存无效";
    }
    student_ptr.reset();
    qInfo() << "[2] 引用次数:" << watcher.use_count();
    if (!watcher.expired()) {
        qInfo() << "内存有效";
    } else {
        qInfo() << "内存无效";
    }
}
/*
[1] 引用次数: 1
内存有效
id 2 析构了
[2] 引用次数: 0
内存无效
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Qt中的智能指针

创建QSharedPointer

int main(int argc, char *argv[]) {
     // 常用声明方式
    QSharedPointer<Student> student = QSharedPointer<Student>(new Student(3));
    // 推荐使用方式
    QSharedPointer<Student> student_create = QSharedPointer<Student>::create(1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

创建QWeakPointer

int main(int argc, char *argv[]) {
    // ......
	// 直接赋值
    QWeakPointer<Student> watcher = student_create;
    // 通过 api 赋值
    QWeakPointer<Student> watcher_api = student_create.toWeakRef();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用智能指针

在使用上无论哪个库中智能指针的使用都是大同小异。

int main(int argc, char *argv[]) {
    // ......
	{
        QSharedPointer<Student> student = QSharedPointer<Student>::create(1);
        {
            QSharedPointer<Student> student_copy = student;
        }
        qInfo() << "离开范围";
    }

    QWeakPointer<Student> watcher;
    QSharedPointer<Student> student_ptr = QSharedPointer<Student>::create(2);
    watcher = student_ptr;
    student_ptr.clear();
    if (watcher.isNull()) {
        qInfo() << "无效内存";
    } else {
        qInfo() << "有效内存";
    }
}
/*
离开范围
"id 1 析构了"
"id 2 析构了"
无效内存
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

返回自身引用

shared_ptr

class Student
    // 继承 std::enable_shared_from_this
    : public std::enable_shared_from_this<Student> {

public:
    Student(int _id);
    ~Student();
        
private:
    int             id_;
};

int main(int argc, char *argv[]) {
    // ......
	std::shared_ptr<Student> student_ptr = std::make_shared<Student>(1);
    qInfo() << student_ptr.use_count();
    {
        // 使用 shared_from_this() 返回自身的引用
        std::shared_ptr<Student> student_copy_ptr = student_ptr->shared_from_this();
        qInfo() << student_ptr.use_count();
    }
}
/*
1
2
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

QSharedPointer

class Student
    // 继承 QEnableSharedFromThis
    : public QEnableSharedFromThis<Student> {

public:
    Student(int _id);
    ~Student();
        
private:
    int             id_;
};

int main(int argc, char *argv[]) {
    // ......
    QSharedPointer<Student> student_ptr = QSharedPointer<Student>::create(1);
    {
        // 使用 sharedFromThis() 返回自身的引用
        QSharedPointer<Student> student_copy_ptr = student_ptr->sharedFromThis();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

智能指针常见错误

二次析构

当有一段内存被释放两次就造成了二次析构问题,在使用智能指针时这种问题比较常见。

  • "裸指针"同时指给两个智能指针
int main(int argc, char *argv[]) {
    // ......
	Student* source_ptr = new Student(1);

    std::shared_ptr<Student> student_ptr_1 = std::shared_ptr<Student>(source_ptr);
    std::shared_ptr<Student> student_ptr_2 = std::shared_ptr<Student>(source_ptr);

    student_ptr_1.reset();
    student_ptr_2.reset();  // 崩溃
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

一般内存无效时的崩溃报错会有_CtrlsValidHeadPointer(block)函数的提示,它是用来检测某段内存是否有效的。

  • 创建的对象被除智能指针以外的机制或者对象释放
int main(int argc, char *argv[]) {
    // ......
	Student source_ptr(1); // 受堆栈控制
    std::shared_ptr<Student> student_ptr 
        = std::shared_ptr<Student>(&source_ptr);	// 受智能指针控制
    student_ptr.reset();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

对于解决二次析构的方式最好就是使用推荐的make_shared方式创建,而不是使用"裸指针"进行赋值。如果是必须这么做的话,那一定要明确创建的"裸指针"是不会被其他机制或者对象控制并被析构才行。

管理数组

使用智能指针在进行数据的清理时调用的是delete直接删除了,如果管理的是一段数组内存,此时就需要在创建智能指针的时候定制一个删除器来控制释放这段内存的方式。

int main(int argc, char *argv[]) {
    // ......
	char * new_array = new char[10];
    std::shared_ptr<char> new_array_ptr = std::shared_ptr<char>(new_array, [](char * array) {
        delete [] array;
        qInfo() << "new 方式创建数组已析构";
    });

    char * malloc_array = (char*) malloc(10);
    std::shared_ptr<char> malloc_array_ptr = std::shared_ptr<char>(malloc_array, [](char * array) {
        free(array);
        qInfo() << "malloc 方式创建数组已析构";
    });

    char ** dimension_array = new char*[10];
    for (int row = 0; row < 10; ++row) {
        for (int col = 0; col < 10; ++col) {
            dimension_array[row] = new char[col];
        }
    }
    std::shared_ptr<char*> dimension_array_ptr = std::shared_ptr<char*>(dimension_array, [](char ** array) {
        for(int row = 0; row < 10; ++row)
            delete [] array[row];
        delete [] array;
        qInfo() << "二维数组已析构";
    });

    new_array_ptr.reset();
    malloc_array_ptr.reset();
    dimension_array_ptr.reset();

}
/*
new 方式创建数组已析构
malloc 方式创建数组已析构
二维数组已析构
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/781605
推荐阅读
相关标签
  

闽ICP备14008679号