C++高级特性:独享智能指针unique_ptr(二)

1、智能指针概念
  • C++11中通过引入智能指针的概念,使得C++程序员不需要手动释放内存

  • 智能指针的种类

    • std::unique_ptr:独占指针,只能有一个智能指针管理内存。
    • std::shared_ptr:共享指针、计数指针;可以多个指针指向同一个内存地址
    • std::weak_ptr:弱指针
    • std::auto_ptr:已经被废掉了
  • C++的指针主要包括两种:

    • 原始指针:也叫裸指针,是最常用的指针,原始指针更加的方便
    • 智能指针:智能指针是原始指针的封装,其优点是会自动分配内存,不用担心潜在的内存泄漏问题;但只是解决了一部分问题,即独占、共享所有权指针的释放、传输
2、独占指针unique_ptr
  • unique_ptr在任何给定的时刻,只能有一个智能指针管理内存
  • 当指针超出作用域时,内存将自动释放
  • 该智能指针只能Move,不能Copy
  • unique_ptr的三种创建方式:
    • 通过已有的原始指针进行创建,此时原始指针和智能指针都指向同一个地址(并不独占),而unique_ptr独占的意思是指只能有一个智能指针管理这一块内存
    • 通过new来创建
    • 通过std::make_unique来创建(推荐方式)
  • unique_ptr可以通过get()函数来获取指针的地址,可以通过->来调用成员函数、通过*来解引用。

先准备一个Cat.h和一个Cat.cpp,如下

#ifndef INC_02_SMART_POINTER_CAT_H
#define INC_02_SMART_POINTER_CAT_H
#include <iostream>
#include <string>
class Cat{
private:
    std::string name{"Mimi"};
public:
    Cat() = default;
    Cat(std::string _name);
    std::string get_Name() const{
        return name;
    }
    void set_Name(const std::string &_name){
        this->name = _name;
    }
    void catInfo() const {
        std::cout << "cat info name: " << name << std::endl;
    }
    virtual ~Cat();
};
#endif //INC_02_SMART_POINTER_CAT_H


Cat::Cat(std::string _name): name(_name){
    std::cout << "Constructor of Cat: " << name << std::endl;
}

Cat:: ~Cat() {
    std::cout << "Destructor of Cat: " << name << std::endl;
}
2.1、基础知识
  • 下面这个代码在{}作用域结束后都不会主动释放分配在heap堆上的c1的内存空间,并且两个c1的内存地址并不一样。
  • 因此在不需要这个指针时需要及时的释放空间,否则只能等整个程序运行结束操作系统来回收。
void test_heap()
{
    Cat *c1 = new Cat("Hello");
    int *i = new int(100);
    std::cout << "test_heap c1 address: " << c1 <<std::endl;
    c1->catInfo();
    {
        Cat *c1 = new Cat("World");
        *i = 123;
        std::cout << "{ } c1 address: " << c1 <<std::endl;
        c1->catInfo();
//        delete c1;
    }
    std::cout << "*i = " << *i << std::endl;
//    delete c1;
//    delete i;
    std::cout << __func__ << std::endl;
}

在这里插入图片描述

2.2、智能指针创建(不推荐)
  • 通过一个创建好的对象cat,让unique_ptr指向cat,此时ptr和cat都指向同一片空间,一旦cat空间释放,ptr也用不了。
  • 这其实是一种bad smell,因为两个指针指向同一个地址,都已经有原始指针了用智能指针再次包装显得毫无意义。
void test_unique_ptr1()
{
    Cat *cat = new Cat("小猫");
    std::unique_ptr<Cat> ptr{cat};
    cat->catInfo();
    ptr->catInfo();
    cat->set_Name("Mimi");
    cat->catInfo();
    ptr->catInfo();
}
/*  输出
Constructor of Cat: 小猫
cat info name: 小猫
cat info name: 小猫
cat info name: Mimi
cat info name: Mimi
test_unique_ptr1									// 函数的作用域结束之前的cout
Destructor of Cat: Mimi							// 由智能指针进行地址的释放
*/
2.3、智能指针创建(可用)
void test_unique_ptr2()
{
    std::unique_ptr<Cat> ptr{new Cat("Hello")};
    ptr->catInfo();
    ptr->set_Name("Mini");
    ptr->catInfo();
    std::cout << __func__ << std::endl;
}

/*	输出
Constructor of Cat: Hello
cat info name: Hello
cat info name: Mini
test_unique_ptr2
Destructor of Cat: Mini					//同上
*/
2.4、智能指针创建(推荐)
void test_unique_ptr3()
{
    std::unique_ptr<Cat> ptr = std::make_unique<Cat>("小猫");
    ptr->catInfo();
    {
        std::unique_ptr<Cat> ptr = std::make_unique<Cat>();					// 调用默认构造函数,name = "Mini"
        ptr->catInfo();
    }
    std::cout << __func__ << std::endl;
}
/*	输出
Constructor of Cat: 小猫
cat info name: 小猫
cat info name: Mimi
Destructor of Cat: Mimi							// {}作用域结束自动释放
test_unique_ptr3
Destructor of Cat: 小猫							// 函数声明周期结束自动释放
*/
2.5、智能指针获取地址
  • 可以通过get函数获取智能指针中的指针地址,&ptr表示获取智能指针的地址
void test_unique_ptr4()
{
    std::unique_ptr<Cat> cat = std::make_unique<Cat>("小猫");
    std::unique_ptr<int> i = std::make_unique<int>(123);
    cat->catInfo();
    std::cout << "*i = " << *i << ", address of i = " << i.get() << std::endl;
    std::cout << "address of cat = " << cat.get() << std::endl;
    std::cout << __func__ << std::endl;
}
/*	输出
Constructor of Cat: 小猫
cat info name: 小猫
*i = 123, address of i = 0x55629f2432b0
address of cat = 0x55629f242e70
test_unique_ptr4
Destructor of Cat: 小猫
*/
3、独占指针unique_ptr与函数调用
3.1、值传递
  • 值传递和赋值重载:由于是独占指针,禁止多个智能指针管理同一个地址空间
  • 原因:其底层源码将拷贝构造和赋值重载函数删除禁用了,因此值传递的方式并不可行。
  • 但是可以通过std::move来移交所有权,移交之后原始ptr1就为空了,再次使用会被报错
void do_pass_value(std::unique_ptr<int> ptr)
{
    std::cout << "value = " << *ptr << ", address = " <<ptr.get() << std::endl;
}

void test_unique_ptr_pass_value()
{
    std::unique_ptr<int> ptr1 = std::make_unique<int>(123);
//    std::unique_ptr<int> ptr2 = ptr1;                     // 报错
//    do_pass_value(ptr1);                                  // 报错
    do_pass_value(std::move(ptr1));
//    std::cout << *ptr1 << std::endl;                        // 段错误
    do_pass_value(std::make_unique<int>(100));              //      ----> do_pass_value(std::move(std::make_unique<int>(100)));
    /*
     // Disable copy from lvalue.
      unique_ptr(const unique_ptr&) = delete;
      unique_ptr& operator=(const unique_ptr&) = delete;
    */
}
3.2、引用传递
  • 传递引用的可以意味着调用的地方拿到的也是同一个地址
  • 但是不会再调用的函数体内进行释放。
void do_pass_ref(std::unique_ptr<Cat>& ptr)
{
    ptr->catInfo();
    ptr->set_Name("小猫");
    // 这里并不会释放ptr,因为是引用
}

void test_unique_ptr_pass_ref()
{
    std::unique_ptr<Cat> ptr = std::make_unique<Cat>();
    do_pass_ref(std::ref(ptr));
    ptr->catInfo();
    std::cout << "结束函数调用: " << __func__  << std::endl;
    // 析构
}
/*		输出
cat info name: Mimi
cat info name: 小猫
结束函数调用: test_unique_ptr_pass_ref
Destructor of Cat: 小猫
*/
3.3、重置提前释放
  • 传递引用的时候可以在函数体内进行reset提前释放
  • 为了避免发生这种情况,可以在函数列表声明中加入const,禁止这个指针指向的修改,但是指针指向的内容可以随便改。
void do_pass_reset(/*const*/ std::unique_ptr<Cat>& ptr)
{
    ptr->catInfo();
    ptr->set_Name("小猫");
    ptr.reset();            //const的ptr无法修改指针指向
}

void test_unique_ptr_pass_reset()
{
    std::unique_ptr<Cat> ptr = std::make_unique<Cat>();
    do_pass_reset(std::ref(ptr));
    // ptr此时为空ptr->catInfo()报错 同时也取不到地址等
    std::cout << "结束函数调用: " << __func__  << std::endl;
}
/*		输出
cat info name: Mimi
Destructor of Cat: 小猫
结束函数调用: test_unique_ptr_pass_reset
*/
  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值