现代C++实战30讲
吴咏炜
前 Intel 资深软件架构师
立即订阅
3628 人已学习
课程目录
已更新 11 讲 / 共 30 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词 | C++这么难,为什么我们还要用C++?
免费
课前必读 | 有关术语发音及环境要求
基础篇 (9讲)
01 | 堆、栈、RAII:C++里该如何管理资源?
02 | 自己动手,实现C++的智能指针
03 | 右值和移动究竟解决了什么问题?
04 | 容器汇编 I:比较简单的若干容器
05 | 容器汇编 II:需要函数对象的容器
06 | 异常:用还是不用,这是个问题
07 | 迭代器和好用的新for循环
08 | 易用性改进 I:自动类型推断和初始化
09 | 易用性改进 II:字面量、静态断言和成员函数说明符
现代C++实战30讲
登录|注册

02 | 自己动手,实现C++的智能指针

吴咏炜 2019-11-25
你好,我是吴咏炜。
上一讲,我们描述了一个某种程度上可以当成智能指针用的类 shape_wrapper。使用那个智能指针,可以简化资源的管理,从根本上消除资源(包括内存)泄漏的可能性。这一讲我们就来进一步讲解,如何将 shape_wrapper 改造成一个完整的智能指针。你会看到,智能指针本质上并不神秘,其实就是 RAII 资源管理功能的自然展现而已。
在学完这一讲之后,你应该会对 C++ 的 unique_ptrshared_ptr 的功能非常熟悉了。同时,如果你今后要创建类似的资源管理类,也不会是一件难事。

回顾

我们上一讲给出了下面这个类:
class shape_wrapper {
public:
explicit shape_wrapper(
shape* ptr = nullptr)
: ptr_(ptr) {}
~shape_wrapper()
{
delete ptr_;
}
shape* get() const { return ptr_; }
private:
shape* ptr_;
};
这个类可以完成智能指针的最基本的功能:对超出作用域的对象进行释放。但它缺了点东西:
这个类只适用于 shape
该类对象的行为不够像指针
拷贝该类对象会引发程序行为异常
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《现代C++实战30讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(78)

  • frazer
    有点吃力了,得反复看几遍

    作者回复: 没关系。我打赌你看的时间肯定没我写稿的时间长。😁

    2019-11-26
    2
    29
  • yuchen
    有深度的专栏,不错。市面上讲解C++的课程一般太基础了。这一章推荐读者可以看看《Professional C++ 4th edition》第九章。

    作者回复: 谢谢。

    《Professional C++》之前没看过,扫了两眼,觉得内容不错,推荐。内容还挺多挺深的,适合决心在 C++ 上深入的同学。

    2019-11-26
    10
  • NEVER SETTLE
    老师这块没想明白
    // 1、调用构造函数
    smart_ptr ptr1{create_shape(shape_type::circle)};

    // 2、因为拷贝构造被禁用,随意编译出错
    smart_ptr ptr2{ptr1}; // 编译出错

    smart_ptr ptr3;

    // 3、没明白为啥会出错
    ptr3 = ptr1; // 编译出错

    // 4、没明白为啥OK,=重载函数的参数不是右值引用呀
    ptr3 = std::move(ptr1); // OK,

    请老师指定 3 与 4

    作者回复: 3. 赋值需要一个对象(不是引用),因而在进入执行前就要引发一个构造。没有合适的构造函数可用。

    4. 同样,要先构造。这回可以用右值引用的构造函数了。

    2019-11-27
    1
    8
  • 流浪地球
    老师您好,问一个比较基础的问题,我理解这个语句
    smart_ptr<shape> ptr1{create_shape(shape_type::circle)}; 是调用ptr1的拷贝构造函数。
    为什么{create_shape(shape_type::circle)}是使用大括号,不应该是小括号吗?
    谢谢

    作者回复: 嘻嘻,我在偷偷地塞进C++11的语法。对象初始化可以统一用大括号。(小括号这儿也行。)

    2019-11-26
    1
    8
  • W.jyao
    对不熟悉C++ 11的程序员来看,有的地方不是很懂

    作者回复: 特别有困难的点可以提出。大部分概念的引入我应该都是有解释的。

    2019-11-26
    7
  • yyfx
    "移动"指针部分有个问题。在使用模板泛化拷贝构造时,p2=p1编译通过,程序异常。测试发现,由于拷贝构造并没有被自动禁用导致。查了下资料,effective45条提到,member templates不影响语言规则,声明member templates用于泛化copy构造时,还需要声明正常的copy构造。

    作者回复: 多谢抓虫。正文已更新。

    这也证明了,修改代码、没有完整测试,是极易招虫的啊……

    2019-11-28
    6
  • hdongdong123
    真的好难啊,呜呜呜

    作者回复: 一遍看不懂,就再看一遍。所有的代码自己试验一下。😀

    学习无捷径。掌握 C++ 不是 30 个课时能解决的事情。一万小时理论对于任何复杂领域都是基本适用的。

    2019-11-27
    6
  • Lilin
    才第二节,就有点吃力了。这篇专栏真是满满的干货

    作者回复: 希望是难,但还能看得下去。哈哈。

    2019-11-26
    6
  • NEVER SETTLE
    老师,问个比较基础的问题,就是运算符重载的时候,我老师搞不清 该返回值 还是 返回引用,这取决于什么?

    作者回复: 如果你搞不清,那多半就是不该返回引用的情况。下面会讲到这个话题。简单来说,大部分情况下,应该直接返回对象值。

    2019-11-27
    1
    4
  • 皓首不倦
    请问下老师smart ptr 的拷贝构造函数为什么有一个泛型版本 还有一个非泛型版本 但是函数体内容又一模一样 不是代码冗余的吗 是有什么特殊设计意图吗 请老师指教下

    作者回复: 这是一个很特殊的、甚至有点恼人的情况。如果没有非泛型版本,编译器看到没有拷贝构造函数,会生成一个缺省的拷贝构造函数。这样,同样类型的smart_ptr的拷贝构造会是错误的。“子类指针向基类指针的转换”这一节里我也提到了这点。这不是我讲智能指针想讲的内容,所以就淡化了。

    2019-11-30
    1
    3
  • Zephyr
    老师,是一直没有更新吗,还是我看不到,我记得是买过这个课程了

    作者回复: 第一周一次性放三讲。后面每周更新三次,一次一讲。

    2019-11-29
    3
  • 小美
    计数器线程安全是不是更好点

    作者回复: 应该的。但我们还没学到通用的C++并发编程呢。

    2019-11-26
    1
    3
  • metalmac.kyle
    通过老师的例子来学习深入学习智能指针,感觉这种案例式都谢谢代入感很强,再难的知识点也不枯燥还能直接学以致用,醍醐灌顶,有种重读csapp的感觉!Cpp一大特性就是各种复杂的语法以及符号设计,这让她被爱的人奉上神坛,恨的人也恨之入骨,指针作为双刃剑充满了恶魔般的魔力,虽然会让人望而却步但也是吸引我一直学习cpp的动力所在,对底层的操作和理解实在让人着魔哈哈。
    言归正传,小小的构造智能指针课蕴含了对Cpp一系列知识的复习巩固,构造函数,运算符重载,指针引用特性,模板等各类知识点的设计与实现,想真正掌握还需要反复上机实践呢,还是应了古话,纸上得来终觉欲知此事须躬行浅!🧐

    作者回复: 没错,相关的知识点很多,一定要多实践才行。

    不过,回过头来看,我觉得我拿智能指针开头,难度可能是有点设高了。哈哈。

    2019-12-01
    2
  • 墨梵
    吴老师讲的还是很好的,听着音频看代码感觉跟不上节奏,需要多看几遍才行。同时,文末放上链接也很不错。希望老师能多放些相关的学习推荐资源,授人以鱼不如授人以渔😀

    作者回复: 我知道、能想得起的参考资料肯定会贴出来。毕竟,三十讲的时间肯定是不够的。大家一定要阅读更多的资料才行。

    2019-11-29
    2
  • NEVER SETTLE
    老师,我想明白了,刚才猛一下懵了

    smart_ptr ptr1{create_shape(shape_type::circle)};
    smart_ptr ptr3;
    /*
    smart_ptr& operator=(smart_ptr rhs) {
        rhs.swap(*this);
        return *this;
    }
    */
    // smart_ptr rhs{ptr1} 发生拷贝构造,所以编译出错
    ptr3 = ptr1; // 编译出错

    // smart_ptr rhs{std::move(ptr1)} 移动构造,所以OK
    ptr3 = std::move(ptr1); // OK,可以

    作者回复: 哈哈,对。

    2019-11-28
    1
    2
  • 贵子
    为什么shared_count类作为smart_ptr的内部类编译不过,而必须作为外部类呢?老师能解释一下吗?谢谢!

    作者回复: 移进去的话,smart_ptr<circle>::shared_count 和 smart_ptr<shape>::shared_count 成了两个完全不相关的类型,它们的指针(在不做强制类型转换时)也不能互相赋值,不好。

    2019-12-13
    1
    1
  • lyfei
    老师你好,那我如果想实现类似的:
    int* xxx = new int[5]; delete xxx;
    使用智能指针怎么做?
    smart_ptr<int> test_ptr(new int[5]);

    作者回复: 这个实现不支持。需要写更多的代码。

    C++17 的 shared_ptr 可以支持这样的写法,但也得明确告诉编译器是数组:

    std::shared_ptr<int[]> ptr1{new int[5]};
    auto ptr2 = std::shared_ptr<int[]>{new int[5]};

    C++20 进一步支持:

    auto ptr3 = std::make_shared<int[]>(5);

    另外,new int[5] 之后用 delete 是不对(好)的。这儿一般还不会出问题,如果是带析构的对象就一定会出问题的。new …[] 之后一定用 delete[] …。

    2019-12-03
    2
    1
  • 禾桃
    smart_ptr& operator=(smart_ptr& rhs)
     { smart_ptr(rhs).swap(*this); return *this; }

    rhs 本身就是smart_ptr的左值引用了,
    在函数题里smart_ptr(rhs),
    貌似应该是rhs.swap(*this)?

    作者回复: 不是。再想想。按你的写法,this指向的对象就被传到rhs来拥有了。这不是=希望做的事情。

    2019-11-30
    3
    1
  • robonix
    老师,文中提到的发生异常,this对象被破坏,具体是指的对象里的什么成员被破坏呢,这个没看的太明白。

    作者回复: 比如,你的对象有两个成员,管理着两个对象,如果你在拷贝赋值时逐个地复制另外一个对象的相应的两个成员对象,在复制第二个时发生了异常,那 this 对象就处于一个奇怪的状态:既没成功完成复制,也没有了原先的对象。

    2019-11-29
    1
  • 浑浑噩噩cium
    类:
    4种构造函数:默认构造,带参数构造,拷贝构造,移动构造
    2种赋值函数:普通赋值函数和移动赋值函数

    作者回复: 现在一般说拷贝赋值和移动赋值了。跟构造的不同方式对应。

    2019-11-28
    1
收起评论
78
返回
顶部