1

现代C++ [1]: 智能指针

 2 years ago
source link: https://no5-aaron-wu.github.io/2022/05/06/cpp-modern-1-SmartPointers/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

旭穹の陋室

现代C++ [1]: 智能指针

发表于2022-05-06|更新于2022-05-23|语言

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象(类)。当栈对象的生存周期结束后(离开对象的作用域,如函数结束),会在析构函数中释放掉申请的内存,不需要手动释放内存空间,从而规避内存泄漏的风险。

auto_ptr

auto_ptr是C++ 98标准的方案,在C++ 11标准中已经弃用,采用独占所有权模式,看下面这个例子:

std::auto_ptr<string> p1 (new string ("Hello World")); 
std::auto_ptr<string> p2;
p2 = p1; // 编译不会报错,但p1会被置成nullptr

auto_ptr的赋值操作在编译时不会报错,但p2剥夺了p1的所有权,使得p1成为一个空指针。所以当程序运行时访问p1将会报错。这也是促使auto_ptr的被弃用的原因。

unique_ptr

作为接替auto_ptr的独占资源所有权的智能指针,其特点如下:

  • 独占式拥有或严格拥有, 保证同一时间内只有一个智能指针可以指向该对象;
  • 常见用法:使用 std::unique_ptr 自动管理内存,基本的RAII(Resource Acquisition Is Initialization)思想;
{
std::unique_ptr<int> uptr = std::make_unique<int>(200);
//...
// 离开 uptr 的作用域的时候自动释放内存
}
  • 采用独占所有权模式,不允许左值赋值操作,但可以接受临时右值(引用)的赋值,即这里的operator=不是copy赋值操作符,而是move赋值操作符;
std::unique_ptr<string> p1 (new string ("Hello World")); 
std::unique_ptr<string> p2;
p2 = p1; // 编译报错
std::unique_ptr<string> p3;
p3 = unique_ptr<string>(new string ("Hello World")); // 允许
std::unique_ptr<string> p4;
p4 = std::move(p1); // 允许

其内在逻辑是左值(拷贝)赋值操作p2 = p1;后,无法合理的处理左值p1的去留。若采用auto_ptr的思路,将p1置为nullptr,则会重走带来潜在的访问null指针而程序崩溃风险的老路,若置之不理,则不满足独占的属性,所以干脆就不允许这样做。而右值赋值则不存在这样的问题,右值本身就是临时的,在赋值给左值后就会被销毁,左值仍然独占资源。std::move就是将一个左值转换成右值引用,从而实现移动语义。移动语义的存在就是在强调转移所有权,即这时候你是清楚地知道p1对资源的所有权发生了转移,所以即便还是出现了p1已经被销毁,不再可用的情况,那么后面乱用p1而导致程序崩溃的代价也应当由乱用者来承担。

  • 可以指向一个数组;
// 实测C++14标准下,以下两种写法都支持
std::unique_ptr<int[]> uptr = std::make_unique<int[]>(10);
// or
std::unique_ptr<int[]> uptr(new int[10]);
  • 可以自定义deleter;
{
struct FileCloser {
void operator()(FILE* fp) const {
if (fp != nullptr) {
fclose(fp);
}
}
};
std::unique_ptr<FILE, FileCloser> uptr(fopen("test_file.txt", "w"));
}
  • 可以使用lambda函数形式的deleter;
{
std::unique_ptr<FILE, std::function<void(FILE*)>> uptr(
fopen("test_file.txt", "w"), [](FILE* fp) {
fclose(fp);
});
}

shared_ptr

std::shared_ptr是引用计数型智慧指针(reference-counting smart pointers,RCSP),其特点如下:

  • 共享式拥有,多个智能指针可以指向相同的对象,该对象和其相关资源会在最后一个指向它的指针被销毁时被释放;
  • 使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数;
{
std::shared_ptr<int> sptr = std::make_shared<int>(200);
assert(sptr.use_count() == 1); // 此时引用计数为 1
{
std::shared_ptr<int> sptr1 = sptr;
assert(sptr.get() == sptr1.get());
assert(sptr.use_count() == 2); // sptr 和 sptr1 共享资源,引用计数为 2
}
assert(sptr.use_count() == 1); // sptr1 已经释放
}
// use_count 为 0 时自动释放内存
  • 可以指向一个数组;
// 实测 std::make_shared<int[]> 这种写法直到C++20标准才支持
std::shared_ptr<int[]> sptr = std::make_shared<int[]>(10);
// or
std::shared_ptr<int[]> sptr(new int[10]);
  • 可以自定义deleter;
{
std::shared_ptr<FILE> sptr(
fopen("test_file.txt", "w"), [](FILE* fp) {
std::cout << "close " << fp << std::endl;
fclose(fp);
});
}
  • 常用的成员函数:
    • use_count:返回引用计数的个数;

    • unique:返回是否为独占所有权( use_count 为 1),C++17标准已弃用;

    • swap:交换两个 shared_ptr 所拥有的对象;

    • reset:放弃拥有对象的所有权或拥有对象的变更(参数为一个新的对象,表示放弃原对象,转而指向新对象),会引起原有对象的引用计数的减少;

    • get:返回内部对象的裸指针,;

  • 出于性能和异常安全的考虑,尽量使用make_shared进行初始化,详细讨论见这里

weak_ptr

share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效(两个指针的引用计数永远不可能下降为0),从而导致内存泄漏。可以看下面这个例子:

class B;	//声明
class A
{
public:
shared_ptr<B> pb_;
~A() { cout << "A delete\n" }
};

class B
{
public:
shared_ptr<A> pa_;
~B() { cout << "B delete\n"; }
};

void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
cout << pb.use_count() << endl; //1
cout << pa.use_count() << endl; //1
pb->pa_ = pa;
pa->pb_ = pb;
cout << pb.use_count() << endl; //2
cout << pa.use_count() << endl; //2
}
//函数退出后,pa,pb指针析构,引用计数各自减1,但两者引用计数还是为1,从而导致A,B的析构函数不会被调用,资源没有被释放,造成内存泄漏

weak_ptr 是为了配合shared_ptr而引入的智能指针,它是一种不控制对象生命周期的智能指针, 指向一个 shared_ptr 管理的对象,只可以从一个shared_ptr或另一个weak_ptr对象构造,是shared_ptr的附属。因此,真正管理对象的还是强引用的shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段,类似于观察者的角色,weak_ptr的构造和析构不会引起引用记数的增加或减少,weak_ptr也无法直接访问对象的成员方法,除非将其转化成强引用的shared_ptr。其特点还有:

  • weak_ptr和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr可以通过调用lock成员函数来转化为shared_ptr;
  • weak_ptr可以用来解决shared_ptr相互引用的死锁问题;
class B;	//声明
class A
{
public:
weak_ptr<B> pb_;
~A() { cout << "A delete\n" }
};

class B
{
public:
shared_ptr<A> pa_;
~B() { cout << "B delete\n"; }
void print() { cout << "B print\n"; }
};

void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
cout << pb.use_count() << endl; //1
cout << pa.use_count() << endl; //1
pb->pa_ = pa;
pa->pb_ = pb;
cout << pb.use_count() << endl; //1
cout << pa.use_count() << endl; //2
}
// 资源B的引用开始就只有1,当pb指针析构时,B的计数变为0,B对象得到释放,调用B的析构函数(打印B delete),
// B释放的同时pa_指针析构,也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放,调用A的析构函数(打印A delete)。
  • 常用的成员函数:

    • expired:用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false;
    • weak_ptr 没有重载operator*operator->,所以不能通过weak_ptr直接访问对象的方法,比如B类的对象中有一个方法print(),我们不能这样访问:pa->pb_->print()
    • lock:用于获取所管理的对象的强引用shared_ptr. 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象的指向与 weak_ptr 相同。故可以用如下方式访问B类对象的print()方法:
    shared_ptr<B> p = pa->pb_.lock();
    p->print();
    • use_count:返回与 shared_ptr 共享的对象的引用计数;
    • reset:将 weak_ptr 置空;
    • weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数;

[1] 详解C++11智能指针

[2] 现代 C++:一文读懂智能指针


Recommend

  • 87
    • blog.51cto.com 6 years ago
    • Cache

    智能指针的原理及其应用-Dream

    所谓智能指针就是自动化管理指针所指向的动态资源的释放。那么智能指针的引用是为了解决哪些问题呢?代码中经常会忘掉释放动态开辟的资源,引用智能指针可用于动态资源管理,资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数中完成资源的分配和初...

  • 38
    • www.freehacker.cn 5 years ago
    • Cache

    智能指针二三事

    C++11中引入智能指针,智能指针主要用来解决资源管理中遇到的各种问题。在引入智能指针之前,我们必须要操作裸指针,裸指针是导致内存问题的罪魁祸首——空悬指针、内存泄漏、分配失败等。一些著名的开源C项目,现在仍然还需要面临着一些由...

  • 24

    关于智能指针的一个疑问

  • 21
    • www.tuicool.com 5 years ago
    • Cache

    窥见C++11智能指针

    导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针,让我们从指针的内存管理中释放出来,几乎消灭所...

  • 11
    • 微信 mp.weixin.qq.com 4 years ago
    • Cache

    Rust入坑指南:智能指针

    在了解了Rust中的所有权、所有权借用、生命周期这些概念后,相信各位坑友对Rust已经有了比较深刻的认识了,今天又是一个连环坑,我们一起来把智能指针刨出来,一探究竟。 智能指针是Rust中一种特殊的数据结构。它与普...

  • 9
    • zhuanlan.zhihu.com 3 years ago
    • Cache

    Rust:智能指针

    Rust:智能指针NONE莫得灵魂的程序员1、什么是智能指针指针...

  • 3

    基于智能指针和RAII的对象内存管理设计 在从C++ std::shared_ptr 原理来看看栈溢出的危害,我提及了C++的智能指针指向被管...

  • 13

    shared_ptr能和基于引用计数的智能指针混用吗? 2020-12-13...

  • 8

    智能指针避坑指南——几种常见的错误用法 2020-11-29...

  • 7

    本文将结合C++11标准中的智能指针std::unique_ptr<>类的简单使用实例,讨论其基本原理,以期快速了解该智能指针类的使用。std::unique_ptr<> 是什么?std::unique_ptr<>...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK