C++11&QT复习 (十七)

文章目录

    • [Day12-1 智能指针回顾](#Day12-1 智能指针回顾)
      • 一、问题回顾
      • 二、智能指针
        • [1. `unique_ptr`](#1. unique_ptr)
        • [2. `shared_ptr`](#2. shared_ptr)
        • [3. 循环引用与 `weak_ptr`](#3. 循环引用与 weak_ptr)
        • [4. `weak_ptr`](#4. weak_ptr)
        • [5.检查 `weak_ptr` 管理的对象是否还存在的方法](#5.检查 weak_ptr 管理的对象是否还存在的方法)
      • 三、删除器(Deleter)
        • [🌟 `std::default_delete`](#🌟 std::default_delete)
        • [✨ 自定义删除器示例(以 `FILE*` 为例)](#✨ 自定义删除器示例(以 FILE* 为例))
          • [✅ 推荐用法(自动释放):](#✅ 推荐用法(自动释放):)
      • 四、智能指针的误用(⚠️易错点)
        • [🔥 示例类 Point](#🔥 示例类 Point)
        • [❌ `unique_ptr` 的误用](#❌ unique_ptr 的误用)
        • [❌ `shared_ptr` 的误用](#❌ shared_ptr 的误用)
        • [✅ 正确使用 `shared_from_this`](#✅ 正确使用 shared_from_this)
          • [✅ 正确的 `addPoint` 函数:](#✅ 正确的 addPoint 函数:)

Day12-1 智能指针回顾

一、问题回顾

  1. 什么是右值引用?有什么特点?右值引用本身是左值还是右值?
  2. 左值是什么?右值是什么?
  3. 移动构造函数与移动赋值函数的形态是什么样的?
  4. std::move函数的实质是什么?是否具备移动的含义?
  5. RAII是什么?具有什么特征?
  6. 对象语义与值语义的区别是什么?
  7. auto_ptr 有什么缺陷?

二、智能指针

1. unique_ptr
  • 独占所有权,不支持拷贝,只支持移动。
  • 可以作为容器元素,因为具备移动语义。
cpp 复制代码
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

void test()
{
    unique_ptr<int> up(new int(10));
    cout << "*up = " << *up << endl;

    // unique_ptr<int> up2 = up; // 错误:拷贝构造被禁用
    // up3 = up;                 // 错误:拷贝赋值被禁用

    unique_ptr<int> up3(new int(34));

    vector<unique_ptr<int>> vec;
    // vec.push_back(up); // 错误:不能拷贝
    vec.push_back(std::move(up)); // OK:使用移动语义
    vec.push_back(std::move(unique_ptr<int>(new int(20)))); // OK

    for (const auto& value : vec) {
        cout << *value << endl;
    }
}
构建右值的两种方式:
  1. 显式调用构造函数创建临时对象
  2. 使用 std::move 将左值转为右值
构建左值的两种方式:
  1. 构造函数创建有名对象:Point pt(1, 2);
  2. 用右值引用绑定右值:Point&& rref = Point(1, 2);
cpp 复制代码
//延长number的生命周期static int number = 10;
void func()
{
    //加大括号,缩短number的生命周期
    {
        int number = 10;//作用域
        cout << "number = " << number << endl;
    }
    int a = 20;
    cout << "a = " << a << endl;
    //业务逻辑
}

2. shared_ptr
  • 支持共享所有权,通过引用计数实现。
  • 支持拷贝和移动,可以安全地放入容器中。
cpp 复制代码
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Point
{
public:
    Point(int ix = 0, int iy = 0) : _ix(ix), _iy(iy) {
        cout << "Point(int, int)" << endl;
    }

    ~Point() {
        cout << "~Point()" << endl;
    }

    Point(const Point& rhs) : _ix(rhs._ix), _iy(rhs._iy) {
        cout << "Point(const Point&)" << endl;
    }

    Point& operator=(const Point& rhs) {
        cout << "Point& operator=(const Point&)" << endl;
        if (this != &rhs) {
            _ix = rhs._ix;
            _iy = rhs._iy;
        }
        return *this;
    }

private:
    int _ix, _iy;
};

void test()
{
    shared_ptr<int> sp(new int(10));
    cout << "*sp = " << *sp << ", use_count = " << sp.use_count() << endl;

    shared_ptr<int> sp2 = sp; // 拷贝共享所有权
    cout << "*sp2 = " << *sp2 << ", use_count = " << sp2.use_count() << endl;

    shared_ptr<int> sp3(new int(34));
    sp3 = sp; // 也可以赋值共享
    cout << "*sp3 = " << *sp3 << ", use_count = " << sp3.use_count() << endl;

    // 放入容器
    shared_ptr<Point> sp4(new Point(1, 2));
    vector<shared_ptr<Point>> vec;
    vec.push_back(std::move(sp4));
    vec.push_back(shared_ptr<Point>(new Point(3, 4)));

    shared_ptr<Point> sp5(new Point(7, 8));
    //实际上在push_back中传左值或者传右值都ok!
    vec.push_back(sp5);

    // 总结:shared_ptr 拥有拷贝和移动语义
}

3. 循环引用与 weak_ptr
  • 问题:循环引用会造成内存泄漏。
  • 解决方案:用 shared_ptr 搭配 weak_ptr,避免引用计数无法归零。
cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

class Child; // 前向声明

class Parent
{
public:
    Parent() { cout << "Parent()" << endl; }
    ~Parent() { cout << "~Parent()" << endl; }

    shared_ptr<Child> pChild;
};

class Child
{
public:
    Child() { cout << "Child()" << endl; }
    ~Child() { cout << "~Child()" << endl; }

    weak_ptr<Parent> pParent; // 使用 weak_ptr 避免引用计数递增
};

void test()
{
    shared_ptr<Parent> parentPtr(new Parent());
    shared_ptr<Child> childPtr(new Child());
 	//解决内存泄漏的问题
    parentPtr->pChild = childPtr;
    childPtr->pParent = parentPtr;

    cout << "parentPtr.use_count() = " << parentPtr.use_count() << endl;
    cout << "childPtr.use_count() = " << childPtr.use_count() << endl;
}

4. weak_ptr
  • 不能直接拥有资源,不增加引用计数。
  • 常用于观察对象生命周期或解决循环引用问题。
cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

class Point
{
public:
    Point(int ix = 0, int iy = 0) : _ix(ix), _iy(iy) {
        cout << "Point(int,int)" << endl;
    }

    ~Point() { cout << "~Point()" << endl; }

    Point(const Point& rhs) : _ix(rhs._ix), _iy(rhs._iy) {
        cout << "Point(const Point&)" << endl;
    }

    Point& operator=(const Point& rhs) {
        cout << "Point& operator=(const Point&)" << endl;
        if (this != &rhs) {
            _ix = rhs._ix;
            _iy = rhs._iy;
        }
        return *this;
    }

private:
    int _ix, _iy;
};

void test()
{
	/*weak_ptr必须从一个shared_ptr或者另一个weak_ptr来构造。
	weak_ptr本身并没有获得资源的所有权,所以它不能直接持有
	通过new创建的对象的所有权,因为它不会增加引用计数,也无法管理对象的释放。*/
	//std::weak_ptr<Point> wp2(new Point(3, 4));
	
	//weak_ptr创建的时候,不能直接与托管资源产生联系
	std::weak_ptr<Point> wp;//空的 weak_ptr;可以创建空对象

	std::shared_ptr<Point> sp(new Point(1, 2));
	wp = sp;// weak_ptr 可以通过 shared_ptr 构造
	std::unique_ptr<Point> pt;// 不允许用 unique_ptr 构造 weak_ptr
}
int main()
{
	test();
	return 0;
}

5.检查 weak_ptr 管理的对象是否还存在的方法
方法名 功能简介
use_count() 返回共享对象的引用计数(观察 shared_ptr 是否还存在)
expired() 判断对象是否已销毁,相当于 use_count() == 0
lock() 尝试将 weak_ptr 升级为 shared_ptr,若对象已销毁则返回空指针

示例代码:

cpp 复制代码
#include <iostream>
#include <memory>
using namespace std;

int main() {
    shared_ptr<int> sp = make_shared<int>(42);
    weak_ptr<int> wp = sp;

    cout << "use_count = " << wp.use_count() << endl;  // 输出 1
    cout << "expired = " << boolalpha << wp.expired() << endl;  // 输出 false

    if (shared_ptr<int> temp = wp.lock()) {
        cout << "对象存在,值为:" << *temp << endl;
    } else {
        cout << "对象已销毁" << endl;
    }

    sp.reset();  // 手动释放 shared_ptr

    cout << "\n释放后:\n";
    cout << "use_count = " << wp.use_count() << endl;  // 输出 0
    cout << "expired = " << wp.expired() << endl;      // 输出 true

    if (shared_ptr<int> temp = wp.lock()) {
        cout << "对象存在,值为:" << *temp << endl;
    } else {
        cout << "对象已销毁" << endl;
    }

    return 0;
}

三、删除器(Deleter)

🌟 std::default_delete

std::default_deleteunique_ptrshared_ptr 默认使用的删除器。其重载了 operator(),分别适用于普通指针和数组指针:

函数形式:
cpp 复制代码
// 1. 删除普通对象
void operator()(T* ptr) const;

// 2. 删除数组对象(只有当 T 是数组类型时)
template<class U>
void operator()(U* ptr) const;
  • (1) 调用 delete ptr;
  • (2) 调用 delete[] ptr;,仅当 U()[] 可以隐式转换为 T()[] 时生效。
  • 如果 U 是不完整类型(incomplete type),程序会报错。
  • 无异常抛出保证(noexcept)

✨ 自定义删除器示例(以 FILE* 为例)

说明:
FILE* 不能使用默认的 delete 释放,必须使用 fclose。所以我们定义一个自定义删除器 FileCloser

✅ 推荐用法(自动释放):
cpp 复制代码
#include <memory>
#include <cstdio>
#include <iostream>
#include <string>

struct FileCloser {
    void operator()(FILE* fp) const {
        if (fp) {
            fclose(fp);
            std::cout << "fclose(fp)" << std::endl;
        }
    }
};

void test() {
    std::string msg = "hello, world\n";
    std::unique_ptr<FILE, FileCloser> uptr(fopen("wd.txt", "a+"));
    
    if (uptr) {
        fwrite(msg.c_str(), 1, msg.size(), uptr.get());
    }
    // 自动调用 FileCloser 析构,自动 fclose
}

void test2() {
    std::string msg = "hello, world\n";
    std::shared_ptr<FILE> sptr(fopen("wd.txt", "a+"), FileCloser());
    //先创建对象再传递参数的方法也是ok的,区别在于一个是传右值,另一个是传右值
	/*FileCloser fc;
	std::shared_ptr<FILE> sptr(fopen("wd.txt", "a+"), fc);*/
    if (sptr) {
        fwrite(msg.c_str(), 1, msg.size(), sptr.get());
    }
    // 自动调用 FileCloser 析构,自动 fclose
}

四、智能指针的误用(⚠️易错点)

🔥 示例类 Point
cpp 复制代码
class Point : public std::enable_shared_from_this<Point> {
public:
    Point(int ix = 0, int iy = 0) : _ix(ix), _iy(iy) {
        std::cout << "Point(int,int)" << std::endl;
    }

    ~Point() { std::cout << "~Point()" << std::endl; }

    Point(const Point& rhs) : _ix(rhs._ix), _iy(rhs._iy) {
        std::cout << "Point(const Point&)" << std::endl;
    }

    Point& operator=(const Point& rhs) {
        std::cout << "Point& operator=(const Point&)" << std::endl;
        if (this != &rhs) {
            _ix = rhs._ix;
            _iy = rhs._iy;
        }
        return *this;
    }

    // ⚠️ 原始写法(误用):返回 this 的裸指针
    Point* addPoint(Point* pt) {
        _ix += pt->_ix;
        _iy += pt->_iy;
        return this;
    }

    void print() const {
        std::cout << "(" << _ix << ", " << _iy << ")" << std::endl;
    }

private:
    int _ix, _iy;
};

unique_ptr 的误用
cpp 复制代码
void test1() {
    Point* pt = new Point(1, 2);
    std::unique_ptr<Point> up(pt);
    std::unique_ptr<Point> up2(pt);  // ❌ 错误!双重释放
}

void test2() {
    std::unique_ptr<Point> up(new Point(1, 2));
    std::unique_ptr<Point> up2(new Point(3, 4));
    up.reset(up2.get());  // ❌ 错误!up 和 up2 同时管理同一指针
}

shared_ptr 的误用
cpp 复制代码
void test3() {
    Point* pt = new Point(1, 2);
    std::shared_ptr<Point> sp(pt);
    std::shared_ptr<Point> sp2(pt);  // ❌ 错误!会导致两次 delete
}

void test4() {
    std::shared_ptr<Point> sp(new Point(1, 2));
    std::shared_ptr<Point> sp2(new Point(3, 4));
    sp.reset(sp2.get());  // ❌ 错误!sp 和 sp2 同时管理同一资源
}

✅ 正确使用 shared_from_this
cpp 复制代码
void testAdd() {
    std::shared_ptr<Point> sp(new Point(1, 2));
    std::shared_ptr<Point> sp2(new Point(3, 4));

    std::shared_ptr<Point> sp3(sp->addPoint(sp2.get()));
    sp3->print();
}

⚠️ 这需要 addPoint() 返回一个 shared_ptr<Point>,否则 sp3 会重复管理裸指针(this)。

✅ 正确的 addPoint 函数:
cpp 复制代码
// 改为返回 shared_ptr 并使用 shared_from_this()
std::shared_ptr<Point> addPoint(Point* pt) {
    _ix += pt->_ix;
    _iy += pt->_iy;
    return shared_from_this();  // 转换为 shared_ptr,安全共享
}
相关推荐
weifexie1 小时前
ruby可变参数
开发语言·前端·ruby
王磊鑫1 小时前
重返JAVA之路-初识JAVA
java·开发语言
千野竹之卫1 小时前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
liuluyang5302 小时前
C语言C11支持的结构体嵌套的用法
c语言·开发语言·算法·编译·c11
凌叁儿2 小时前
python保留关键字详解
开发语言·python
明飞19873 小时前
C_内存 内存地址概念
c语言·开发语言
代码不停3 小时前
Java中的异常
java·开发语言
牛奶咖啡.8543 小时前
第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 A 组真题
c语言·数据结构·c++·算法·蓝桥杯
兮兮能吃能睡3 小时前
Python中的eval()函数详解
开发语言·python
狄加山6754 小时前
Qt模型-视图架构
开发语言·qt