IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++11智能指针 -> 正文阅读

[C++知识库]c++11智能指针

c++11智能指针

auto_ptr

template< class T > class auto_ptr;
(1)	(deprecated in C++11) (removed in C++17)
template<> class auto_ptr<void>;
(2)	(deprecated in C++11) (removed in C++17)

auto_ptr这个智能指针已经被弃用了,但是还是要看一看它为什么被弃用了?它的缺点是什么?以便于更好的理解后续的智能指针!

代码示例:

// 拷贝复制
auto_ptr<int> sp1(new int(10));
auto_ptr<int> sp2(sp1);
if (sp1.get() != nullptr)
{
    cout << "sp1 is not empty!" << endl;
}
else
{
    cout << "sp1 is empty!" << endl;
}

if (sp2.get() != nullptr)
{
    cout << "sp2 is not empty!" << endl;
}
else
{
    cout << "sp2 is empty!" << endl;
}

// 赋值复制
auto_ptr<int> sp3(new int(10));
auto_ptr<int> sp4;
sp4 = sp3;
if (sp3.get() != nullptr)
{
    cout << "sp3 is not empty!" << endl;
}
else
{
    cout << "sp3 is empty!" << endl;
}

if (sp4.get() != nullptr)
{
    cout << "sp4 is not empty!" << endl;
}
else
{
    cout << "sp4 is empty!" << endl;
}

运行结果:

sp1 is empty!
sp2 is not empty!
sp3 is empty!
sp4 is not empty!

可以通过结果看到,**在复制前sp1和sp3所托管的内存空间再复制后发生了转移!**显然在一些容器中使用auto_ptr会造成很多问题,如对容器中的元素赋值传递,这样会造成很多元素被置为空指针,造成错误!c++11标准引入了三种新型的智能指针:unique_ptr、shared_ptr、weak_ptr!

unique_ptr

unique_ptr 对其持有的堆内存具有唯一拥有权,也就是说该智能指针对资源(即其管理的堆内存)的引用计数永远是 1,unique_ptr 对象在销毁时会释放其持有的堆内存。

对unique_ptr初始化常见的方式

// 方式一
unique_ptr<int> sp1(new int(10));

// 方式二
unique_ptr<int> sp2;
sp2.reset(new int(10));

// 方式三
unique_ptr<int>sp3 = make_unique<int>(10);  (c++14)

推荐使用方式三,因为方式三更加安全。如果不支持c++14,当然也可以自己实现方式三:

template<typename Tp, typename... Args>
make_unique(Args&&... args)
{ return unique_ptr<Tp>(new Tp(std::forward<Args>(args)...)); }

鉴于auto_ptr的前车之鉴,unique_ptr禁止复制语义,为了达到效果,unique_ptr的拷贝构造函数和复制运算符函数均被标记为=delete!

代码示例:

unique_ptr<int> sp1 = make_unique<int>(10);

unique_ptr<int> sp2(sp1); // error 

unique_ptr<int> sp3; 
sp3 = sp1;               // error 

不过禁止复制语义也存在特例,例如可以通过一个函数返回一个unique_ptr

unique_ptr<int> func(int val)
{
    unique_ptr<int> sp1 = make_unique<int>(10);
    return sp1;
}

int main()
{
    unique_ptr<int> sp = func(1);
    return 0;
}

虽然unique_ptr不能被赋值,但是也可以将所持有的堆内存转移给另外一个unique_ptr。就是使用std::move

代码示例:

unique_ptr<int> sp1 = make_unique<int>(10);
unique_ptr<int> sp2(move(sp1));

unique_ptr<int> sp3;
sp3 = move(sp2);
if (sp1.get() == nullptr)
{
    cout << "sp1 is empty!" << endl;
}
else
{
    cout << "sp1 is not empty!" << endl;
}

move将sp1所持有的堆内存(值为10)转移给sp2,在将sp2转移给sp3。最后sp1、和sp2不在持有堆内存的引用,变成一个空的智能指针对象。当然并不是所有对象move的操作都有意义,只有实现了移动构造函数或者移动赋值运算符(operator=)的类才可以,而unique_ptr正好实现了二者。

unique_ptr还可以持有一组堆对象,示例如下:

// 创建10个int类型的堆对象
// 形式一
unique_ptr<int[]> sp1(new int[10]);

// 形式而二
unique_ptr<int[]> sp2;
sp2.reset(new int[10]);

// 形式三
unique_ptr<int[]> sp3(make_unique<int[]>(10));

for (int i = 0; i < 10; ++i)
{
    sp1[i] = i;
    sp2[i] = 2 * i;
    sp3[i] = 3 * i;
}

for (int i = 0; i < 10; ++i)
{
    cout << sp1[i] << ", " << sp2[i] << ", " << sp3[i] << endl;
}

在默认情况下,智能指针对象在析构时只会释放其持有的堆内存,但是加入这块内存代表的对象还对应一种需要回收的资源(如socket、文件句柄等),我们可以通过给智能指针自定义回收函数来实现资源回收。

unique_ptr的自动释放语法规则:

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;                          (1)	(since C++11)
template <
    class T,
    class Deleter
> class unique_ptr<T[], Deleter>;            (2)	(since C++11)

T:需要释放的对象类型
Deleter:自定义的函数指针

示例代码:

class Socket
{
public:
    Socket() {}
    ~Socket() {}

    void close() {}
};

int main()
{
    auto deletor = [](Socket *pSocket)
    {
        // 关闭句柄
        pSocket->close();
        delete pSocket;
    };

    unique_ptr<Socket, void (*)(Socket * pSocket)> sp(new Socket(), deletor);
    
    // 也可以使用decltype(deletor)让编译器自动推导deletor类型
    unique_ptr<Socket, decltype(deletor)> sp(new Socket(), deletor);

    return 0;
}

shared_ptr

? unique_ptr对其持有的资源具有独占性,而shared_ptr持有的资源可以在多个shared_ptr之间共享,每多一个shared_ptr对资源的引用,资源的引用计数就会增加1,在每一个指向该资源的shared_ptr对象析构时,资源引用计数都会-1,最后一个shared_ptr对象析构时,若发现资源引用计数为0,那么将释放所持有的的资源。

? 多个线程之间递增和减少资源的引用计数是安全的,但并不意味着多个线程同时操作shared_ptr管理的资源是安全的,shared_ptr提供了一个use_count方法来获取当前管理的资源的引用计数。

初始化shared_ptr示例:

方式一:
shared_ptr<int> sp1(new int(123));
方式二
shared_ptr<int> sp2;
sp2.reset(new int(123));
方式三(优先使用)
shared_ptr<int> sp3;
sp3 = make_shared<int>(123);

使用示例:

#include <iostream>
#include <memory>

using namespace std;

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

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

void test()
{
    shared_ptr<A> sp1(new A());
    cout << "sp1 use count = " << sp1.use_count() << endl;

    cout << "--将sp1托管的对象拷贝给sp2--" << endl;
    shared_ptr<A> sp2(sp1);
    cout << "sp1 use count = " << sp1.use_count() << endl;
    cout << "sp2 use count = " << sp2.use_count() << endl;

    cout << "--sp2释放对资源对象A的引用--" << endl;
    sp2.reset();
    cout << "sp1 use count = " << sp1.use_count() << endl;
    cout << "sp2 use count = " << sp2.use_count() << endl;

    cout << "--将sp1赋值给sp1--" << endl;
    shared_ptr<A> sp3 = sp1;
    cout << "sp1 use count = " << sp1.use_count() << endl;
    cout << "sp3 use count = " << sp3.use_count() << endl;
}

int main(int argc, char **argv)
{
    test();
    return 0;
}

运行结果:

A()
sp1 use count = 1
--将sp1托管的对象拷贝给sp2--
sp1 use count = 2
sp2 use count = 2
--sp2释放对资源对象A的引用--
sp1 use count = 1
sp2 use count = 0
--将sp1赋值给sp1--
sp1 use count = 2
sp3 use count = 2
~A()

shared_ptr循环引用问题,代码示例

#include <iostream>
#include <memory>

using namespace std;

class B;
class A
{
public:
    A()
    {
        cout << "A()" << endl;
    }

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

    shared_ptr<B> sp1;
};

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

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

    shared_ptr<A> sp2;
};
int main(int argc, char **argv)
{
    test1();
    return 0;
}

运行结果:

A()
B()

可以看到只有两个对象的构造函数调用,没有出现析构函数,说明此时造成了内存泄漏。为什么会导致这种问题呢。因为shared_ptr是强的智能指针,两个对象都互相指向,两个对象任意销毁都需要先销毁另外一个对象,造成引用计数永远不为0,所以造成了内存泄漏。为此c++11中还引入了weak_ptr来解决这种问题。

weak_ptr

weak_ptr是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只提供对其管理的资源一个访问手段,它出现的目的就是为了协助shared_ptr。

使用代码示例:

#include <iostream>
#include <memory>
using namespace std;

void test()
{
    shared_ptr<int> sp1(new int(123));
    cout << "sp1.use_count = " << sp1.use_count() << endl;

    weak_ptr<int> sp2(sp1);
    cout << "sp1.use_count = " << sp1.use_count() << endl;
    cout << "sp2.use_count = " << sp2.use_count() << endl;

    weak_ptr<int> sp3 = sp1;
    cout << "sp1.use_count = " << sp1.use_count() << endl;
    cout << "sp3.use_count = " << sp3.use_count() << endl;

    weak_ptr<int> sp4(sp2);
    cout << "sp2.use_count = " << sp2.use_count() << endl;
    cout << "sp4.use_count = " << sp4.use_count() << endl;
}

int main(int argc, char **argv)
{
    test();

    return 0;
}

运行结果:
sp1.use_count = 1
sp1.use_count = 1
sp2.use_count = 1
sp1.use_count = 1
sp3.use_count = 1
sp2.use_count = 1
sp4.use_count = 1

weak_ptr不管理所引用的资源的生命周期,所以引用的资源可能在某个时刻失效,所以我们在使用weak_ptr引用资源时,需要知道该资源是否有效。可以使用expired来检测,如果返回的是truc,表示引用的资源已经失效,返回false表示资源仍有效。此时可以使用lock方法来讲weak_ptr转换到shared_ptr对象后继续操作该资源。

#include <iostream>
#include <memory>
using namespace std;

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

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

void test()
{
    // weak_ptr<A> wp(new A()); // weak_ptr不能直接获取资源
    weak_ptr<A> wp;

    // 块作用域
    {
        shared_ptr<A> sp(new A());
        wp = sp;
        if (wp.expired())
        {
            cout << "wp托管的资源已被销毁!" << endl;
        }

        // 下面这种写法是错的,因为weak_ptr没有实现operator->、operator*、
        // operator bool()操作,因此不能通过weak_ptr对象直接判断其引用的资源是否存在
        // if (wp){}

        // 正确写法
        // lock()创建一个shared_ptr管理引用的对象,若没有托管对象则返回nullptr
        shared_ptr<A> sp1 = wp.lock();
        if (sp1)
        {
            cout << "sp1.use_count = " << sp1.use_count() << endl;
        }
        else
        {
            cout << "weak_ptr所托管的对象已经被销毁!" << endl;
        }
    }
    cout << "---------------------" << endl;
    if (wp.expired())
    {
        cout << "wp托管的资源已被销毁!" << endl;
    }
    shared_ptr<A> sp1 = wp.lock();
    if (sp1)
    {
        cout << "sp1.use_count = " << sp1.use_count() << endl;
    }
    else
    {
        cout << "weak_ptr所托管的对象已经被销毁!" << endl;
    }
}

int main(int argc, char **argv)
{
    test();
    return 0;
}

处理shared_ptr循环引用问题时,只需要将一个类中的指针改成weak_ptr即可。

智能指针的大小

需要注意的是,有同学看到指针,64位系统中直接就认为8个字节。其实不然,拿我本机测试(gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04))

unique_ptr<int> up;
shared_ptr<int> sp;
weak_ptr<int> wp;
cout << "sizeof(unique_ptr) = " << sizeof(up) << endl;
cout << "sizeof(shared_ptr) = " << sizeof(sp) << endl;
cout << "sizeof(weak_ptr) = " << sizeof(wp) << endl;

输出结果:
sizeof(unique_ptr) = 8
sizeof(shared_ptr) = 16
sizeof(weak_ptr) = 16

可以看到shared_ptr和weak_ptr大小是原始指针的二倍。unique_ptr和和原始指针一样大。

智能指针的使用注意事项

  1. 使用智能指针托管一个对象后,不要使用原始指针去操作它,错误案例

    #include <iostream>
    #include <memory>
    using namespace std;
    
    class A
    {
    public:
        A()
        {
            cout << "A()" << endl;
        }
    
        ~A()
        {
            cout << "~A()" << endl;
        }
    };
    int main()
    {
        A *pa = new A();
        unique_ptr<A> up(pa);
        delete pa;
        
        return 0;
    }
    运行结果:
    A()
    ~A()
    ~A()
    free(): double free detected in tcache 2
    已放弃 (核心已转储)
    

    除了使用三个智能指针的get()方法来获取原始指针。

  2. 不同场景下使用的指针

    1. 如不需要共享资源,那么优先适应unique_ptr,反之使用shared_ptr。
    2. 如果不需要管理对象的生命周期,使用weak_ptr。
  3. 避免操作某个引用资源已经释放的智能指针

  4. 作为类成员变量,应该优先使用前置声明

如果本文对你有帮助,记得一键三连哦,一键三连笑哈哈,代码能力顶呱呱!

本人能力有限,如有错误,望不吝指正;原创不易,欢迎转载,转载请注明出处!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-14 23:31:36  更:2022-04-14 23:31:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:10:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码