C++新经典 | C++ 查漏补缺(智能指针)

news2024/11/27 9:43:41

目录

一、动态分配

1.初始化的三种方式

2. 释放内存

(1)悬空指针

 3.创建新工程与观察内存泄漏

二、深入了解new/delete关键字

1.new和delete

2.operator new()和operator delete()

3.申请和释放一个数组

三、智能指针

 1.shared_ptr

(1)常规初始化

(2)make_shared

(3)引用计数

(3.1)引用计数增加

(3.2)引用计数减少

(4)shared_ptr指针常用操作

(4.1)use_count成员函数

(4.2)unique成员函数

(4.3)reset成员函数

(4.4)get成员函数

(4.5)swap成员函数

(5)自定义删除器

(6)使用建议、陷进与禁忌

(6.0)优先使用make_shared构造智能指针

(6.1)慎用裸指针

(6.2)慎用get返回的指针

(6.3)用enable_shared_from_this返回this

(6.4)避免互相循环引用

(7)移动语义

2.weak_ptr

 (1)weak_ptr常用操作

(1.1)use_count成员函数

(1.2)expired成员函数

(1.3)reset成员函数

(1.4)lock成员函数

(2)尺寸问题

(3)控制块

3.unique_ptr

(1)常规初始化

(2)make_unique

(3)常用操作

(3.1)移动语义

(3.2)release成员函数

(3.3)reset成员函数

(3.4)get成员函数

(3.5)转换成shared_ptr类型

(4)尺寸问题

(5)删除器

四、智能指针的选择


一、动态分配

        由程序员自己用new来为对象分配内存(new返回的是该对象的指针)的方式叫作动态分配。这种动态分配内存的方式就相当于对内存直接进行管理,具体地说就是针对new和delete的应用。

    int *p = new int;   //*p为一个随机值

        这个分配的int对象其实没有名,或者说是一个无名对象,但是new可以返回一个指向该对象的指针,可以通过这个指针操作这个无名的int对象。

1.初始化的三种方式

        很多内置类型(如int类型)对象,如若不主动提供初值,其初值就是未定义的(不确定的),如上方所示代码。

        还有一种初始化的说法叫值初始化。自己定义的类,在new该类的对象时,所谓的值初始化是没有意义的。所以有意义的是类似int的这种内置类型。看一看值初始化的写法:

    int *p1 = new int();//值初始化,*p为0

        当然,也可以new一个对象的同时进行初始化

    int *p2 = new int(100);//new一个对象的同时进行初始化,*p为100

2. 释放内存

        delete一块内存,只能delete一次,不可以delete多次。当然,可以多次delete一个空指针,但并没有什么实际意义。

        提倡在delete一个指针后会把该指针设置为空(p=nullptr;)。因为一个指针即便被delete,该指针中依然保存着它所指向的那块动态内存的地址,此时该指针叫悬空指针(也叫野指针,程序员不能再操作这个指针所指向的内存),那么如果给该指针一个nullptr,表示该指针不指向任何内存,是一个好的习惯。

        const对象值不能被改变,但可以被delete:

   const int *p = new int(100);  
   delete p;
   p = nullptr;

(1)悬空指针

悬空指针(也被称为野指针)是指向已经被释放或者删除的内存块的指针。在C++中,这通常发生在以下几种情况:

1. 当一个对象被删除,但还有一个或多个指针仍然引用它。
2. 当函数返回后,局部变量的地址仍然被保留。
3. 当动态分配的内存已经被释放,但是仍有指针引用。

使用悬空指针可能会导致程序崩溃、数据损坏和不可预测的行为。要避免这种情况,应该确保在删除对象或释放内存后将所有相关的指针设置为nullptr,并且不要返回局部变量的地址。

int* ptr = new int(5); // 动态分配内存
delete ptr; // 释放内存
ptr = nullptr; // 将ptr设置为nullptr以避免成为悬空指针

        请注意,在C++11及更高版本中,在delete之后将智能指针设为空可以自动防止悬空引用。

std::unique_ptr<int> ptr(new int(5)); // 使用智能指针
ptr.reset(); // 释放内存并将ptr设置为空

 3.创建新工程与观察内存泄漏

        可以用“MFC应用”来观察内存泄漏。“MFC应用”的特点是:在程序运行结束(退出)的时候,如果这个程序有内存泄漏,“MFC应用”能够报告出来。

        “Detected memory leaks!”字样,翻译成中文表示出现了内存泄漏。而且泄漏的地方有2处,1处是8字节,1处是28字节,总共泄漏36字节。

二、深入了解new/delete关键字

1.new和delete

        new和delete都是关键字(也叫运算符/操作符),都不是函数。malloc和free用于C语言编程中,而new和delete用于C++编程中。

        new/delete和malloc/free最明显的区别之一就是使用new生成一个类对象时系统会调用该类的构造函数,使用delete删除一个类对象时系统会调用该类的析构函数(释放函数)。既然有调用构造函数和析构函数的能力,这就意味着new和delete具备针对堆所分配的内存进行初始化(把初始化代码放在类的构造函数中)和释放(把释放相关的代码放在类的析构函数中)的能力,而这些能力是malloc和free所不具备的。

2.operator new()和operator delete()

        operator new(…)与operator delete(…)实际上是函数。那么,这两个函数和new/delete操作符有什么关系呢?

(1)new运算符做了两件事:①分配内存;②调用构造函数初始化该内存。new运算符是怎样分配内存的呢?new运算符就是通过调用operator new(…)来分配内存的。这个函数能直接调用,但一般很少有人这样做。

(2)delete运算符也做了两件事:①调用析构函数;②释放内存。delete运算符就是通过调用operator delete()来释放内存的。

3.申请和释放一个数组

        为数组动态分配内存时,往往需要用到[],如new[…],而释放数组时,往往也要用到[],如delete[…],这意味着,往往new[]和delete[]要配套使用。

class My_Class {
public:
    My_Class()
    {
        std::cout << "构造函数"<<std::endl;
    }
    ~My_Class()
    {
        std::cout << "析构函数"<<std::endl;
    } 
};
int main()
{
    My_Class *myClass = new My_Class[2];  //调用两次构造函数
    delete[] myClass;                     //调用两次析构函数

    return 0;
}

        为什么这个调用会导致调用两次类的析构函数?系统如何知道new的时候new出了几个数组元素(类对象)呢?

        C++的做法是在分配数组空间时多分配了4字节的大小,专门保存数组的大小,在delete []时就可以取出这个数组大小的数字,就知道了需要调用析构函数多少次了。

       内置类型如int类型,delete的时候不存在调用类的析构函数的说法(只有类对象或类对象数组delete的时候才存在调用类的析构函数的说法),所以new的时候系统并没有多分配4字节内存。

  • 如果把类的析构函数注释掉,则用new[]为对象数组分配内存,而用单独的delete来释放内存,不会异常。
class My_Class {
public:
    My_Class()
    {
        std::cout << "构造函数"<<std::endl;
    }
    //~My_Class()
    //{
    //    std::cout << "析构函数"<<std::endl;
    //} 
};
int main()
{
    My_Class *myClass = new My_Class[2];  //调用两次构造函数
    delete myClass;                       //没问题

    return 0;
}
  • 如果类书写了自己的析构函数,则用new[]为对象数组分配内存,而用单独的delete来释放内存,就会报异常。 
class My_Class {
public:
    My_Class()
    {
        std::cout << "构造函数"<<std::endl;
    }
    ~My_Class()
    {
        std::cout << "析构函数"<<std::endl;
    } 
};
int main()
{
    My_Class *myClass = new My_Class[2];  //调用两次构造函数
    delete myClass;                       //会出异常

    return 0;
}

        报异常的原因是,代码行“delete myClass;”做了两件事:

(1)调用一次类的析构函数。new的时候创建的是两个对象,调用的是两次构造函数,而释放的时候调用的是一次析构函数,虽然不致命,但也存在后遗症(如类的构造函数中如果分配了内存,指望在析构函数中释放内存,那么如果少执行一次析构函数,就会直接导致内存的泄漏)。

(2)调用“operator delete(myClass);”来释放内存。系统所报的异常,其实就是执行这行代码的调用导致的。就是因为多分配4字节的问题导致释放的内存空间错乱。 

         所以,如果一个对象,是用new[]分配内存,而却用delete(而不是delete[])来释放内存,那么这个对象满足的条件是:对象的类型是内置类型(如int类型)或者是无自定义析构函数的类类型。

三、智能指针

        直接new一个对象的方式返回的是一个对象的指针,这个对象的指针很多人称其为裸指针。所谓裸,指的就是这种直接new出来的指针没有经过任何包装的意思。显然,这种指针功能强大,使用灵活,同时开发者也必须全程负责维护,一不小心就容易犯错,一旦使用错误,造成的问题可能就极大。

        C++标准库中有4种智能指针,即std::auto_ptr、std::unique_ptr、std::shared_ptr、std::weak_ptr。每一个都有适用的场合,它们的存在就是为了帮助程序员管理动态分配的对象(new出来的对象)的生命周期,既然智能指针能够管理对象的生命周期,所以能够有效地防止内存的泄漏。

目前std::auto_ptr已经完全被std::unique_ptr所取代,所以不要再使用std::auto_ptr。C++11标准中也反对再使用(弃用)std::auto_ptr了。

auto_ptr有些使用上的限制(缺陷),如不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。

  • shared_ptr是共享式指针的概念。多个指针指向同一个对象,最后一个指针被销毁时,这个对象就会被释放。
  • weak_ptr这个智能指针是用来辅助shared_ptr工作的。
  • unique_ptr是一种独占式指针的概念,同一个时间内只有一个指针能够指向该对象,当然,该对象的拥有权(所有权)是可以移交出去的。

 1.shared_ptr

        shared_ptr指针采用的是共享所有权来管理所指向对象的生存期。所以,对象不仅仅能被一个特定的shared_ptr所拥有,而是能够被多个shared_ptr所拥有。多个shared_ptr指针之间相互协作,从而确保不再需要所指对象时把该对象释放掉。

        在决定是否采用这种智能指针时首先得想一个问题:所指向的对象是否需要被共享,如果需要被共享(类似于多个指针都需要指向同一块内存),那就使用shared_ptr,如果只需要独享(只有一个指针指向这块内存),就建议使用unique_ptr智能指针,因为shared_ptr虽然额外开销不大,但毕竟为了共享还是会有一些额外开销。

        shared_ptr的工作机制是使用引用计数,每一个shared_ptr指向相同的对象(内存),所以很显然,只有最后一个指向该对象的shared_ptr指针不需要再指向该对象时,这个shared_ptr才会去析构所指向的对象。

(1)常规初始化

    std::shared_ptr<std::string> p1;   //默认初始化,智能指针里面保存的是一个空指针nullptr(可以指向类型为string的对象)。

    std::shared_ptr<std::string> p2 = new std::string("abc"); //错误,智能指针是被explicit修饰的,不支持隐式转换(带等号一般为隐式转换)

    std::string *str = new std::string("abc");
    std::shared_ptr<std::string> p3 (str);     //虽然裸指针可以初始化智能指针,但是不推荐智能指针和裸指针穿插使用
 
    std::shared_ptr<std::string> p4(new std::string("abc"));  //正确

(2)make_shared

        make_shared函数这是一个标准库里的函数模板,被认为是最安全和更高效的分配和使用shared_ptr智能指针的一个函数模板。它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared_ptr。

        注意:使用make_shared方法生成shared_ptr对象,那就没有办法自定义删除器了。

std::shared_ptr<int> p = std::make_shared<int>(10);

(3)引用计数

(3.1)引用计数增加

        每个shared_ptr都关联着一个引用计数,当在下述几种情况下,所有指向这个对象的shared_ptr引用计数都会增加1。

  • 用p1智能指针来初始化p2智能指针,就会导致所有指向该对象(内存)的shared_ptr引用计数全部增加1。
    std::shared_ptr<A> p2 = std::make_shared<A>(10, 10);
    std::shared_ptr<A> p3(p2);                 //也可写成这样:auto p3(p2);
    std::cout << p3.use_count() << std::endl;  //2
  • 把智能指针当成实参往函数里传递(函数执行完毕后,这个指针的引用计数会恢复
    )。但是如果传递引用作为形参进来,则引用计数不会增加。
void Test(std::shared_ptr<int> num)
{
    std::cout << num.use_count() << std::endl;       //3
}

void main()
{
    std::shared_ptr<int> p = std::make_shared<int>(10); //1
    auto p1(p);//2
    Test(p1);  //3
    std::cout << p1.use_count() << std::endl;        //2
}
void Test(std::shared_ptr<int>& num)
{
    std::cout << num.use_count() << std::endl;       //2
}

void main()
{
    std::shared_ptr<int> p = std::make_shared<int>(10);//1
    auto p1(p);//2
    Test(p1);
}
  • 作为函数的返回值并且有变量接收。
std::shared_ptr<int> Test(std::shared_ptr<int>& num) //注意是引用
{
    std::cout << num.use_count() << std::endl;       //2
    return num;
}

void main()
{
    std::shared_ptr<int> p = std::make_shared<int>(10);
    auto p1(p);
    auto p2 = Test(p1);
    std::cout << p1.use_count() << std::endl;        //3
}
(3.2)引用计数减少
  • 给shared_ptr赋一个新值,让该shared_ptr指向一个新对象。
  • 局部的shared_ptr离开其作用域。
  • 当一个shared_ptr引用计数变为0,它会自动释放自己所管理的对象。

(4)shared_ptr指针常用操作

(4.1)use_count成员函数

        该成员函数用于返回多少个智能指针指向某个对象。该成员函数主要用于调试目的,效率可能不高。

(4.2)unique成员函数

        是否该智能指针独占某个指向的对象,也就是若只有一个智能指针指向某个对象,则unique返回true,否则返回false

(4.3)reset成员函数
  • 当reset不带参数时。若pi是唯一指向该对象的指针,则释放pi指向的对象,将pi置空。若pi不是唯一指向该对象的指针,则不释放pi指向的对象,但指向该对象引用计数会减1,同时将pi置空。
  • 当reset带参数(一般是一个new出来的指针)时。若pi是唯一指向该对象的指针,则释放pi指向的对象,让pi指向新内存。若pi不是唯一指向该对象的指针,则不释放pi指向对象,但指向该对象的引用计数会减1,同时让pi指向新内存。
  • 空指针也可以通过reset来重新初始化。
(4.4)get成员函数

        p.get():返回智能指针p中保存的指针。小心使用,若智能指针释放了所指向的对象,则返回的这个指针所指向的对象也就变得无效了。

        为什么要有这样一个函数呢?主要是考虑到有些函数的参数需要的是一个内置指针(裸指针),所以需要通过get取得这个内置指针并传递给这样的函数。但要注意,不要delete这个get到的指针,否则会产生不可预料的后果。

(4.5)swap成员函数

        用于交换两个智能指针所指向的对象。当然,因为是交换,所以引用计数并不发生变化。

(5)自定义删除器

        智能指针能在一定的时机自动删除它所指向的对象。默认情况下,shared_ptr正是使用delete运算符作为默认的删除它所指向的对象的方式。

        程序员可以指定自己的删除器,这样当智能指针需要删除一个它所指向的对象时,它不再去调用默认的delete运算符来删除对象,而是调用程序员为它提供的删除器来删除它所指向的对象。常规情况下,默认的删除器工作的是挺好,但是有一些情况需要自己指定删除器,因为默认的删除器处理不了——用shared_ptr管理动态数组的时候,就需要指定自己的删除器,默认的删除器不支持数组对象。

        其实,定义数组时在尖括号“<>”中都加“[]”,即使不写自己的删除器,也能正常释放内存。

std::shared_ptr<int[]> p4(new int[10]{0});

         shared_ptr指定删除器的方法比较简单,一般只需要在参数中添加具体的删除器函数名即可(注意,删除器是一个单形参的函数)。删除器也可以是一个lambda表达式。

void My_Deleter(int* a)
{
    delete a;
}
void main()
{
    ///自定义删除器   lambda表达式形式
    std::shared_ptr<int> p4(new int(10), [](int *p) {
        delete p;
        });

    ///自定义删除器  函数指针形式
    std::shared_ptr<int> p5(new int(10), My_Deleter);
}

        还有一点值得注意:就算是两个shared_ptr指定的删除器不相同,只要它们所指向的对象相同,那么这两个shared_ptr也属于同一个类型。同一个类型有个明显的好处是可以放到元素类型为该对象类型的容器里,方便操作。

    std::vector<std::shared_ptr<int>> vec{ p4,p5 };

(6)使用建议、陷进与禁忌

(6.0)优先使用make_shared构造智能指针

        优先使用make_shared构造智能指针,编译器内部会有一些针对内存分配的特殊处理,所以会使make_shared效率更高,如消除重复代码、改进安全性等。

    std::shared_ptr<std::string> p1(new std::string("Hello world"));

        上面这行代码会至少分配两次内存,第一次是为string类型的实例分配内存(从而保存字符串"Hello world"),第二次是在shared_ptr构造函数中给该shared_ptr控制块分配内存。

        针对make_shared,编译器只会分配一次内存,这个内存分配的足够大,既能保存字符串("Hello world")又能够同时保存控制块:

std::shared_ptr<std::string> p2 = std::make_shared<std::string>("Hello world");
(6.1)慎用裸指针

        把一个普通裸指针绑到了一个shared_ptr上,那内存管理的责任就交给了这个shared_ptr,这时就不应该再使用裸指针(内置指针)访问shared_ptr指向的内存了。

        裸指针虽然可以初始化shared_ptr,但注意不要用裸指针初始化多个shared_ptr。即便用裸指针,直接传递new运算符而不是传递一个裸指针变量。

(6.2)慎用get返回的指针

        永远不要用get得到的指针来初始化另外一个智能指针或者给另外一个智能指针赋值。

(6.3)用enable_shared_from_this返回this

        enable_shared_from_this是一个类模板,它的类型模板参数就是继承它的子类的类名。该类模板中有一个弱指针weak_ptr,这个弱指针能够观测this,调用shared_from_this方法的时候,这个方法内部实际是调用了这个weak_ptr的lock方法,lock方法会让shared_ptr指针计数+1,同时返回这个shared_ptr。

(6.4)避免互相循环引用

        互相循环引用会导致内存泄漏。

(7)移动语义

        在shared_ptr智能指针里,也存在移动语义的概念。复制会使shared_ptr的强引用计数递增,而移动并不会使shared_ptr的强引用计数递增。

void main()
{
    std::shared_ptr<int> p1 = std::make_shared<int>(10);  
    std::shared_ptr<int> p2 = std::move(p1); 
    std::cout << p1.use_count() << std::endl;    //0
    std::cout << p2.use_count() << std::endl;    //1
}

2.weak_ptr

        weak_ptr这个智能指针是用来辅助shared_ptr工作的。weak翻译成中文是“弱”的意思,弱和强是反义词,那“强”指的又是谁呢?容易想象,强指的就是shared_ptr,弱指的就是weak_ptr。

        将weak_ptr绑定到shared_ptr上并不会改变shared_ptr的引用计数(更确切地说,weak_ptr的构造和析构不会增加或者减少所指向对象的引用计数)。当shared_ptr需要释放所指向的对象时照常释放,不管是否有weak_ptr指向该对象。这就是weak“弱”的原因——能力弱(弱共享/弱引用:共享其他的shared_ptr所指向的对象),控制不了所指向对象的生存期。

        弱引用可以理解成是监视shared_ptr(强引用)的生命周期用的,是一种对shared_ptr的扩充,不是一种独立的智能指针,不能用来操作所指向的资源,所以它看起来像是一个shared_ptr的助手(旁观者)这种感觉。所以它的智能也就智能在能够监视到它所指向的对象是否存在了。当然还有些额外用途。

        shared_ptr指向的对象代表的引用统统指的都是强引用,而weak_ptr所指向的对象代表的引用统统都是弱引用。程序员是不能使用weak_ptr来直接访问对象的,必须要使用一个叫作lock的成员函数,lock的功能就是检查weak_ptr所指向的对象是否还存在,如果存在,lock能够返回一个指向共享对象的shared_ptr(当然原shared_ptr引用计数会+1),如果不存在,则返回一个空的shared_ptr。

    std::shared_ptr<int> p6 = std::make_shared<int>(10); // shared_ptr指向的对象代表的引用统统指的都是强引用
    std::weak_ptr<int> w_ptr(p6);                        // weak_ptr所指向的对象代表的引用统统都是弱引用,p6引用计数还是1

    auto temp = w_ptr.lock();                            //如果w_ptr所指的对象存在,并且有temp接收,p6引用计数加1
    if (temp)
    {
        std::cout << p6.use_count() << std::endl;        //2
    }

 (1)weak_ptr常用操作

(1.1)use_count成员函数

        获取与该弱指针共享对象的其他shared_ptr的数量,或者说获得当前所观测资源的引用计数(强引用计数)。

(1.2)expired成员函数

        是否过期的意思,若该指针的use_cout为0(表示该弱指针所指向的对象已经不存在),则返回true,否则返回false。换句话说,判断所观测的对象(资源)是否已经被释放。

(1.3)reset成员函数

        将该弱引用指针设置为空,不影响指向该对象的强引用数量,但指向该对象的弱引用数量会减1。

(1.4)lock成员函数

        获取所监视的shared_ptr。

(2)尺寸问题

        weak_ptr尺寸(就是大小或者sizeof)和shared_ptr对象尺寸一样大,是是裸指针的2倍。在当前Visual Studio的x86平台下,一个裸指针的sizeof值是4字节。weak_ptr和shared_ptr的尺寸都是8字节,这8字节中包含了两个裸指针:

  • 第一个裸指针指向的是该智能指针所指向的对象。
  • 第二个裸指针指向一个很大的数据结构(控制块)。这个控制块里面有 :
    • 所指对象的引用计数。
    • 所指对象的弱引用计数。
    • 其他数据,如自定义的删除器的指针(如果指定了自定义删除器)等。

        控制块实际是由shared_ptr创建出来的,而后,当使用shared_ptr对象创建weak_ptr对象时,weak_ptr对象也指向了这个控制块。

(3)控制块

        控制块是跟着类的,它的大小可能十几字节甚至更多,如指定了删除器等,那么这里的字节数可能会稍微变大一些。这个控制块是由第一个指向某个指定对象的shared_ptr来创建。因为weak_ptr对象也是通过shared_ptr创建出来的,因此weak_ptr对象也使用这个由shared_ptr创建的控制块。

        控制块创建的时机:

  • make_shared:分配并初始化一个对象,返回指向此对象的shared_ptr。所以make_shared总是创建一个控制块。
  • 使用裸指针来创建一个shared_ptr对象时。(前面讲解shared_ptr使用陷阱时强调,不要用裸指针初始化多个shared_ptr,否则会产生多个控制块,也就是多个引用计数,导致析构所指向的对象时会析构多次,彻底混乱,导致程序运行异常。)

3.unique_ptr

        谈到使用智能指针,一般来说,最先想到和优先考虑选择使用的还是unique_ptr智能指针。unique_ptr智能指针是一种独占式智能指针,或者理解成专属所有权这种概念也可以,也就是说,同一时刻,只能有一个unique_ptr指针指向这个对象(这块内存)。当这个unique_ptr被销毁的时候,它所指向的对象也会被销毁。

(1)常规初始化

    std::unique_ptr<A> p = new A(10,10);        //错误,智能指针被explicit修饰,不支持隐式转换(带等号一般为隐式转换)
    std::unique_ptr<A> p(new A(10,10));           //正确

    A* a = new  A(10, 10);                        //不推荐智能指针和裸指针穿插用
    std::unique_ptr<A> p1(a);

(2)make_unique

        C++11中没有make_unique函数,但是C++14里提供了这个函数。与常规初始化比,也是要优先选择使用make_unique函数,这代表着更高的性能。当然,和make_shared一样,如果想使用删除器,那么就不能使用make_unique函数,因为make_unique不支持指定删除器的语法。

    std::unique_ptr<A> pp = std::make_unique<A>(10, 10);  //C++14,推荐

(3)常用操作

(3.1)移动语义

        unique_ptr不允许复制、赋值等动作,是一个只能移动不能复制的类型。可以通过std::move来将一个unique_ptr转移到其他的unique_ptr。

        unique_ptr智能指针不能复制。但有一个例外,如果这个unique_ptr将要被销毁,则还是可以复制的,最常见的是从函数返回一个unique_ptr。

    std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");

    std::unique_ptr<std::string> uni_ptr2 = std::move(uni_ptr1);//转移后,uni_ptr1为空
(3.2)release成员函数

        放弃对指针的控制权(切断了智能指针和其所指向的对象之间的联系),返回指针(裸指针),将智能指针置空。返回的这个裸指针可以手工delete释放,也可以用来初始化另外一个智能指针,或者给另外一个智能指针赋值。

    std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");
    std::string *str = uni_ptr1.release();
    delete str;
(3.3)reset成员函数

        当reset不带参数时,释放智能指针指向的对象,并将智能指针置空。当reset带参数时,释放智能指针原来所指向的内存,让该智能指针指向新内存。

(3.4)get成员函数

        返回智能指针中保存的对象(裸指针)。小心使用,若智能指针释放了所指向的对象,则返回的对象也就变得无效了。

(3.5)转换成shared_ptr类型

        如果unique_ptr为右值,就可以将其赋给shared_ptr。模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr,shared_ptr将接管原来归unique_ptr所拥有的对象。

        转换方法可通过函数返回unique_ptr,并使用shared_ptr来接收的方式;也可以通过std::move方法。

        一个shared_ptr创建的时候,它的内部指针会指向一个控制块。把unique_ptr转换成shared_ptr的时候,系统也会为这个shared_ptr创建控制块。因为unique_ptr并不使用控制块,只有shared_ptr才使用控制块。

(4)尺寸问题

        unique_ptr也基本和裸指针一样:足够小,操作速度也够快。但是,如果增加了删除器,那unique_ptr的尺寸可能不变化,也可能有所变化。

  • 如果删除器是lambda表达式这种匿名对象,unique_ptr的尺寸就没变化。
  • 如果删除器是一个函数,unique_ptr的尺寸就会发生变化。

        unique_ptr尺寸变大肯定对效率有一定影响,所以把一个函数当作删除器,还是要慎用。这一点与shared_ptr不同,shared_ptr是不管指定什么删除器,其大小都是裸指针的2倍。

(5)删除器

        unique_ptr的删除器相对复杂一点,先要在类型模板参数中传递进去删除器类型名,然后在参数中再给具体的删除器名。

        创建shared_ptr的时候,删除器不同,但指向类型(所指向对象的类型)相同的shared_ptr,可以放到同一个容器中。但到了unique_ptr这里,如果删除器不同,则就等于整个unique_ptr类型不同,那么,这种类型不同的unique_ptr智能指针没有办法放到同一个容器中去的。

void My_Deleter(int* a)
{
    delete a;
    a = nullptr;
}

void main()
{
    ///自定义删除器   lambda表达式形式
    using fun = void(*)(int*);
    std::unique_ptr<int,fun> p4(new int(10), [](int *p) {
        delete p;
        p = nullptr;
        });

    ///自定义删除器  函数指针形式
    typedef void(*fun)(int*);
    std::unique_ptr<int,fun> p5(new int(10), My_Deleter);
}

四、智能指针的选择

        如果程序中要使用多个指向同一个对象的指针,应选择shared_ptr。

        如果程序中不需要多个指向同一个对象的指针,则可使用unique_ptr。

        总之,在选择的时候,优先考虑使用unique_ptr,如果unique_ptr不能满足需求,再考虑使用shared_ptr。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1042364.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

十大排序——2.归并排序

这篇文章我们来讲一下十大排序中的归并排序。 目录 1.概述 2.代码实现 3.总结 1.概述 归并排序主要是运用了归并的思想。 下面具体的来讲一下归并排序的整个流程和思想。 首先&#xff0c;给你一个无序的数组&#xff0c;要求你对它进行归并排序。归并排序首先需要将这个…

开始报名!龙蜥社区走进 Arm MeetUp 议程硬核剧透来了

「龙蜥社区“走进系列”MeetUp」是由龙蜥社区与生态合作伙伴联合主办的系列月度活动&#xff0c;每期走进一家企业&#xff0c;聚焦龙蜥社区和合作伙伴的技术、产品和创新动态&#xff0c;展示硬核技术&#xff0c;共建繁荣生态。 《聚焦 Arm 性能提升&#xff0c;助力龙蜥生态…

修炼k8s+flink+hdfs+dlink(一:安装hdfs)

一&#xff1a;安装jdk&#xff0c;并配置环境变量。 在对应的所有的节点上进行安装。 mkdir /opt/app/java cd /opt/app/java wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24http%3A%2F%2Fwww.oracle.com% 2F; oraclelicenseaccept-securebackup…

为什么手动采购管理会危及你的工作流程?

你是否经常在找到一张发票后&#xff0c;却被告知该发票未经财务批准&#xff1f;多少次因为关键文件放错位置或处理不当而丢失订单&#xff1f; 如果答案是 “经常”&#xff0c;那么你还在采用的传统手动采购策略是时候彻底改变了。 2023年了&#xff0c;仍旧依赖手动采购管…

STL排序、拷贝和替换算法

目录 常用排序算法sort 常用排序算法random_shuffle 常用排序算法merge 常用排序算法reverse 常用拷贝和替换算法copy 常用拷贝和替换算法replace 常用拷贝和替换算法replace_if 常用拷贝和替换算法swap 常用排序算法sort sort(iterator begp iterator end,_Pred); //…

羧基荧光素-氨基.盐酸盐,FAM-NH2.HCl,138589-19-2

产品简介&#xff1a;5-FAM-NH2.HCl(羧基荧光素-氨基.盐酸盐)其中异硫氰酸荧光素(FITC)具有比较高的活性,通常来说,在固相合成过程中引 入该种荧光基团相对于其他荧光素要更容易,并且反应过程中不需要加入活化试剂。可以用来修饰蛋白质、多肽以及其他活性基团材料或者小分子。 …

【LeetCode热题100】--54.螺旋矩阵

54.螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 按层遍历 可以将矩阵看成若干层&#xff0c;首先输出最外层的元素&#xff0c;其次输出次外层的元素&#xff0c;直到输出最内层的元素。 对于每层&…

视频汇聚/安防监控平台EasyCVR指定到新的硬盘进行存储录像,如何自动挂载该磁盘?

TSINGSEE青犀视频监控汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&…

在Pyppeteer中实现反爬虫策略和数据保护

爬虫是我们获取互联网数据的神奇工具&#xff0c;但是面对越来越严格的反爬虫措施&#xff0c;我们需要一些我们获取数据的利器来克服这些障碍。本文将带您一起探索如何使用Pyppeteer库来应对这些挑战。 Pyppeteer是一个基于Python的无头浏览器控制库&#xff0c;它提供了与Chr…

Godot信号教程(使用C#语言)| 创建自定义信号 | 发出自定义信号 | 使用代码监听信号

文章目录 信号是什么连接信号使用编辑器连接信号使用代码连接信号Lambda 自定义信号声明信号发射信号带参数的信号 其他文章 信号是什么 在Godot游戏引擎中&#xff0c;信号是一种用于对象之间通信的重要机制。它允许一个对象发出信号&#xff0c;而其他对象可以连接到这个信号…

K8S:pod控制器详解

文章目录 一.pod控制器的基础&#xff11;.pod概念及分类&#xff12;.什么是Pod控制器及其功用&#xff13;.pod控制器有多种类型&#xff08;1&#xff09;ReplicaSet&#xff08;2&#xff09;Deployment&#xff08;3&#xff09;DaemonSet&#xff08;4&#xff09;Statef…

视频截取gif动画怎么操作?轻松一键快速视频转gif

如何截取视频中的一段制作gif动画&#xff1f;当我们看到电影、电视剧等短视频中的某一个片段截取出来做成gif动画&#xff0c;应该如何操作呢&#xff1f;这时候&#xff0c;只需要使用gif制作&#xff08;https://www.gif.cn/&#xff09;工具&#xff0c;不用下载软件&#…

解决typescript报错:不能将类型xxx分配给类型xxx

现象&#xff1a; 这种情况是因为组件传参时&#xff1a; 等号左右两边的数据类型不能严格匹配一致造成的 等号左边data, 查看一下被传参的子组件ProductList的内部data属性: 可以看到data的类型是 &#xff1a; Product[] 而右边的shoppingCartItems来自于&#xff1a; redu…

配置pytorchGPU虚拟环境-python3.7

cuda版本的pytorch包下载地址戳这里 winR->输入cmd->输nvcc -V回车 cuda 11.0 输入以下命令来查找 CUDA 的安装路径&#xff1a; Windows: where nvcc 输入以下命令来查找 cuDNN 的版本号&#xff1a; Windows: where cudnn* cuDNN 8.0 本机安装的是cuda 11.0&…

JS前端树形Tree数据结构使用

前端开发中会经常用到树形结构数据&#xff0c;如多级菜单、商品的多级分类等。数据库的设计和存储都是扁平结构&#xff0c;就会用到各种Tree树结构的转换操作&#xff0c;本文就尝试全面总结一下。 如下示例数据&#xff0c;关键字段id为唯一标识&#xff0c;pid为父级id&am…

稳压器【TPS6283810YFPR 3A】汽车类、TPS629203QDRLRQ1,TPS74550PQWDRVRQ1采用小型6 引脚 WSON 封装。

一、TPS6283810、采用 1.2mm x 0.8mm WCSP 封装的 2.4V-5.5V 输入、6 引脚 3A 微型降压转换器 TPS6283810YFPR是一款高频同步降压转换器&#xff0c;经优化具有小解决方案尺寸和高效率等特性。该器件的输入电压范围为2.4V 至 5.5V&#xff0c;支持常用电池技术。该转换器在中等…

如何永久关闭WPS任务窗口?

1、按住任务窗口上的浮动按钮&#xff0c;将其拖出来成悬浮窗口。 第二步&#xff0c;使用火绒弹窗拦截&#xff0c;选中弹出的窗口&#xff0c;进行拦截。注意&#xff1a;拦截次数为2次。即进行2次操作。 操作两次后&#xff0c;弹窗被拦截&#xff0c;此时Word文档改为双页显…

众佰诚:现在的抖音小店赚钱是真的吗

随着互联网的飞速发展&#xff0c;社交媒体平台如抖音已经成为了许多人赚钱的新途径。在抖音上&#xff0c;越来越多的小店涌现出来&#xff0c;各种各样的产品被推销给用户。但是&#xff0c;人们普遍关心的一个问题是&#xff1a;现在的抖音小店赚钱是真的吗? 首先&#xff…

新闻报道的未来:自动化新闻生成与爬虫技术

概述 自动化新闻生成是一种利用自然语言处理和机器学习技术&#xff0c;从结构化数据中提取信息并生成新闻文章的方法。它可以实现大规模、高效、多样的新闻内容生产。然而&#xff0c;要实现自动化新闻生成&#xff0c;首先需要获取可靠的数据源。这就需要使用爬虫技术&#…

【慕伏白教程】 Linux 深度学习服务器配置指北

文章目录 镜像烧录系统安装系统配置常用包安装 镜像烧录 下载 Ubuntu 镜像 Ubuntu 桌面版 下载烧录工具 balenaEtcher 准备至少 8G 的 空白U盘 开始烧录 系统安装 开机进入BIOS&#xff0c;修改U盘为第一启动 选择 Try or Install Ubuntu 往下拉&#xff0c;选择 中文&a…