0%

2020年3月16日 下午5:11

补充:2020年5月20日 下午7:26

  1. 所谓的异常catch其实就是一个函数调用

定义:

编译器会自动调用析构函数,包括在函数执行发生异常的情况。在发生异常时对析构函数的调用,还有一个专门的术语,叫栈展开(stack unwinding)

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>

class Obj {
public:
Obj() { puts("Obj()"); }
~Obj() { puts("~Obj()"); }
};

void foo(int n)
{
Obj obj;
if (n == 42)
throw "life, the universe and everything";
}

int main()
{
try {
foo(41);
foo(42);
}
catch (const char* s) {//从这里就可以看出来,所谓的异常catch其实就是一个函数调用
puts(s);
}
}

执行代码的结果是:

1
2
3
4
5
Obj()
~Obj()
Obj()
~Obj()
life, the universe and everything

也就是说,不管是否发生了异常,obj 的析构函数都会得到执行。

2020年3月16日 下午5:08

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void foo(int n)
{

}

void bar(int n)
{
int a = n + 1;
foo(a);
}

int main()
{

bar(42);

}

调用过程

过程描述:

  • 在我们的示例中,栈是向上增长的。在包括 x86 在内的大部分计算机体系架构中,栈的增长方向是低地址,因而上方意味着低地址。任何一个函数,根据架构的约定,只能使用进入函数时栈指针向上部分的栈空间。
    • 第一部分:当函数调用另外一个函数时
      • 会把参数也压入栈里(我们此处忽略使用寄存器传递参数的情况)
      • 然后把下一行汇编指令的地址压入栈,并跳转到新的函数。
    • 第二部分:新的函数进入后
      • 首先做一些必须的保存工作
      • 然后会调整栈指针,分配出本地变量所需的空间
      • 随后执行函数中的代码
    • 并在执行完毕之后
      • 根据调用者压入栈的地址,返回到调用者未执行的代码中继续执行。
  • 注意到了没有,本地变量所需的内存就在栈上,跟函数执行所需的其他数据在一起。当函数执行完成之后,这些内存也就自然而然释放掉了。我们可以看到:
    • 栈上的分配极为简单,移动一下栈指针而已。
    • 栈上的释放也极为简单,函数执行结束时移动一下栈指针即可。
    • 由于后进先出的执行过程,不可能出现内存碎片
  • 栈帧(stack frame)
    • 顺便说一句,图 2 中每种颜色都表示某个函数占用的栈空间。这部分空间有个特定的术语,叫做栈帧(stack frame)。GCC 和 Clang 的命令行参数中提到 frame 的,如 -fomit-frame-pointer,一般就是指栈帧。
  • 前面例子的本地变量是简单类型,C++ 里称之为 POD 类型(Plain Old Data)。对于有构造和析构函数的非 POD 类型,栈上的内存分配也同样有效,只不过 C++ 编译器会在生成代码的合适位置,插入对构造和析构函数的调用。

2020年3月16日 下午4:58

堆:自由存储区

  • 英文是 heap,在内存管理的语境下,指的是动态分配内存的区域。这个堆跟数据结构里的堆不是一回事。这里的内存,被分配之后需要手工释放,否则,就会造成内存泄漏。
  • C++ 标准里一个相关概念是自由存储区,英文是 free store,特指使用 new 和 delete 来分配和释放内存的区域。一般而言,这是堆的一个子集:
    • new 和 delete 操作的区域是 free store
    • malloc 和 free 操作的区域是 heap
  • new 和 delete 通常底层使用 malloc 和 free 来实现,所以 free store 也是 heap。鉴于对其区分的实际意义并不大,在本专栏里,除非另有特殊说明,我会只使用堆这一术语。

RAII

  • 完整的英文是 Resource Acquisition Is Initialization,是 C++ 所特有的资源管理方式。有少量其他语言,如 D、Ada 和 Rust 也采纳了 RAII,但主流的编程语言中, C++ 是唯一一个依赖 RAII 来做资源管理的。
  • RAII 依托栈和析构函数来对所有的资源——包括堆内存在内——进行管理
  • 对 RAII 的使用,使得 C++ 不需要类似于 Java 那样的垃圾收集方法,也能有效地对内存进行管理。RAII 的存在,也是垃圾收集虽然理论上可以在 C++ 使用,但从来没有真正流行过的主要原因。

内存管理器的功能

  • 让内存管理器分配一个某个大小的内存块
  • 让内存管理器释放一个之前分配的内存块
  • 让内存管理器进行垃圾收集操作,寻找不再使用的内存块并予以释放

2020年3月16日 下午4:55

了解一下std::move

  • std::move - cppreference.com
  • std::move 用于_指示_对象 t 可以“被移动”,即允许从 t 到另一对象的有效率的资源传递。

test01_unique_ptr.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <utility>  // std::swap/move
#include "shape.h" // shape/shape_type/create_shape

template <typename T>
class smart_ptr {
public:
explicit smart_ptr(T* ptr = nullptr)
: ptr_(ptr) {}
~smart_ptr()
{
delete ptr_;
}
smart_ptr(smart_ptr&& other)
{
ptr_ = other.release();
}
smart_ptr& operator=(smart_ptr rhs)
{
rhs.swap(*this);
return *this;
}

T* get() const { return ptr_; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
T* release()
{
T* ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void swap(smart_ptr& rhs)
{
using std::swap;
swap(ptr_, rhs.ptr_);
}

private:
T* ptr_;
};

int main()
{
smart_ptr<shape> ptr1{create_shape(shape_type::circle)};
//smart_ptr<shape> ptr2{ptr1}; // Cannot compile
smart_ptr<shape> ptr3;
//ptr3 = ptr1; // Cannot compile
ptr3 = std::move(ptr1); // OK
smart_ptr<shape> ptr4{std::move(ptr3)}; // OK
}

2020年3月16日 下午4:41

总结1:

  1. 在多线程下什么场景进行对象操作不安全?
    1. 线程可以释放:不同线程之间,不知道自己所指的内存对应的对象是否还活着,有可能被其中一个线程释放了,那其他线程此时在调用就报错了
    2. 线程不可以释放:要不就是在堆中,所有线程都别释放,这样显然不成
    3. 这两种方法都不好!
  2. 为什么smart_ptr可以实现对多线程下对象生命周期的管理?
    1. 我们从下面的代码中在单线程下的测试输出可以看出,smart_ptr的本质是对象资源的管理,将对象析构的权利给予到第三方的smart_ptr。
    2. 对象内存的资源释放与作用域{},所在的线程都是无关的,至于引用计数值=0相关
      1. 当smart_ptr对象超出自己的作用域{}之后,即使自己被析构了,但是smart_ptr指向的对象引用计数值!=0,那么这个对象还是没有被析构的
      2. 析构所在的线程不一定是资源对象诞生的线程P20
    3. smart_ptr完成的是对资源对象的引用计数,计数的能力是不受线程并发的影响的!所以,smart_ptr可以直接用到多线程场景下。
  3. 为什么对象中的mutex不能解决?
    1. 编写线程安全的类不是难事,使用同步原语(eg:mutex)来保护内部状态,但是对象的生死不能由对象拥有的mutex进行保护。
    2. 析构函数会将对象中的mutex成员变量进行销毁

总结2

  1. smart_ptr是一种类似于解决单点登录的”设计“问题,smart_ptr的本质是对象资源的管理,将对象析构的权利给予到第三方的smart_ptr,这样大家都别抢,我smart_ptr保证对每一位用户负责。
  2. smart_ptr核心目的有2:
    1. 在域外能够自动析构对象,做到RAII
    2. 共享计数:
      1. 也就说这个计数能是每个smart_ptr对象有各自的计数,实现的方式就像是单点登录中redis的作用,在多个tomcat之间共享
      2. 一个smart_ptr加、减,其他的相同指向的smart_ptr都可以看到
  3. 代码实现的关键:
    1. smart_ptr<circle> ptr1(new circle());这里面new circle()产生的对象是分配在堆上的,不受main()函数作用域限制,需要我们手动进行释放
    2. smart_ptr.h中class smart_ptr的实现的关键在于:
      1. 多种构造函数:绿色
      2. 指针常用运算符的重载:红色
      3. shared_count是一个指针类型:黄色

代码:

  • 这份代码包含了shard_ptr 和 unique_ptr
  • main.cpp*
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <stdio.h>      // printf/puts
    #include "shape.h" // shape/shape_type/create_shape
    #include "smart_ptr.h" // smart_ptr


    int main()
    {
    // 唯一的一个对象实体new circle()
    smart_ptr<circle> ptr1(new circle());
    printf("use count of ptr1 is %ld\n", ptr1.use_count());

    smart_ptr<shape> ptr2;
    printf("use count of ptr2 was %ld\n", ptr2.use_count());
    // 赋值运算符重载
    ptr2 = ptr1;
    printf("use count of ptr2 is now %ld\n", ptr2.use_count());
    if (ptr1) {
    puts("ptr1 is not empty");
    }
    // 测试smart_ptr的强制类型转换
    smart_ptr<circle> ptr3 = dynamic_pointer_cast<circle>(ptr2);
    printf("use count of ptr3 is %ld\n", ptr3.use_count());
    // 移动构造函数调用
    smart_ptr<shape> ptr4 = std::move(ptr2);
    printf("use count of ptr4 is %ld\n", ptr4.use_count());
    printf("use count of ptr2 is now %ld\n", ptr2.use_count());
    // 指向相同对象的smart_ptr有相同的conut
    printf("use count of ptr1 is now %ld\n", ptr1.use_count());
    printf("use count of ptr2 is now %ld\n", ptr2.use_count());// 由于我们使用了移动语义,这里就成0了
    printf("use count of ptr3 is now %ld\n", ptr3.use_count());
    /** 输出
    circle()
    use count of ptr1 is 1
    use count of ptr2 was 0
    use count of ptr2 is now 2
    ptr1 is not empty
    use count of ptr3 is 3
    use count of ptr4 is 3
    use count of ptr2 is now 0
    use count of ptr1 is now 3
    use count of ptr2 is now 0
    use count of ptr3 is now 3
    ~circle()
    * ***/
    }

shape.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#ifndef SHAPE_H
#define SHAPE_H

#include <stdexcept> // std::logic_error
#include <stdio.h> // puts

enum class shape_type {
circle,
triangle,
rectangle,
};

class shape {
public:
virtual ~shape() {}
};

class circle : public shape {
public:
circle() { puts("circle()"); }
~circle() { puts("~circle()"); }
};

class triangle : public shape {
public:
triangle() { puts(“triangle()”); }
~triangle() { puts("~triangle()"); }
};

class rectangle : public shape {
public:
rectangle() { puts("rectangle()"); }
~rectangle() { puts("~rectangle()"); }
};

inline shape* create_shape(shape_type type)
{
switch (type) {
case shape_type::circle:
return new circle();
case shape_type::triangle:
return new triangle();
case shape_type::rectangle:
return new rectangle();
}
throw std::logic_error("shape type is invalid");
}

#endif // SHAPE_H

smart_ptr.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#ifndef SMART_PTR_H
#define SMART_PTR_H

#include <atomic> // std::atomic
#include <utility> // std::swap

class shared_count {
public:
shared_count() noexcept
: count_(1)
{
}
void add_count() noexcept
{
count_.fetch_add(1, std::memory_order_relaxed);
}
long reduce_count() noexcept
{
return --count_;
}
long get_count() const noexcept
{
return count_;
}

private:
std::atomic_long count_;
};

template <typename T>
class smart_ptr {
public:
// 没有则报错:fatal error: ‘ptr_’ is a private member of ‘smart_ptr<circle>’
// 错误原因是模板的各个实例间并不天然就有 friend 关系,因而不能互访私有成员
template <typename U>
friend class smart_ptr;
// 在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换
// 1.explicit 关键字只能用于类内部的构造函数声明上。
// 2.explicit 关键字作用于单个参数的构造函数。
// 3.在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换
explicit smart_ptr(T* ptr = nullptr)
: ptr_(ptr)
{
if (ptr) {
shared_count_ = new shared_count();
}
}
~smart_ptr()
{
if (ptr_ && !shared_count_->reduce_count()) {
delete ptr_;
delete shared_count_;
}
}

smart_ptr(const smart_ptr& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
template <typename U>
smart_ptr(const smart_ptr<U>& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
template <typename U>
smart_ptr(smart_ptr<U>&& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
shared_count_ = other.shared_count_;
other.ptr_ = nullptr;
}
}
template <typename U>
smart_ptr(const smart_ptr<U>& other, T* ptr) noexcept
{
ptr_ = ptr;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
// 这里面有一个知识点:参数(smart_ptr rhs)是会触发一次构造函数的执行的
// 触发的是:smart_ptr(smart_ptr<U>&& other)这个重载的构造函数
smart_ptr& operator=(smart_ptr rhs) noexcept
{
rhs.swap(*this);
return *this;
}

T* get() const noexcept
{
return ptr_;
}
long use_count() const noexcept
{
if (ptr_) {
return shared_count_->get_count();
} else {
return 0;
}
}
void swap(smart_ptr& rhs) noexcept
{
using std::swap;
swap(ptr_, rhs.ptr_);
swap(shared_count_, rhs.shared_count_);
}

T& operator*() const noexcept
{
return *ptr_;
}
T* operator->() const noexcept
{
return ptr_;
}
operator bool() const noexcept
{
return ptr_;
}

private:
T* ptr_;
shared_count* shared_count_;
};

template <typename T>
void swap(smart_ptr<T>& lhs, smart_ptr<T>& rhs) noexcept
{
lhs.swap(rhs);
}

template <typename T, typename U>
smart_ptr<T> static_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = static_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

template <typename T, typename U>
smart_ptr<T> reinterpret_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = reinterpret_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

template <typename T, typename U>
smart_ptr<T> const_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = const_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

template <typename T, typename U>
smart_ptr<T> dynamic_pointer_cast(const smart_ptr<U>& other) noexcept
{
T* ptr = dynamic_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}

#endif // SMART_PTR_H

2020年3月16日 下午2:56

总结:

  • 这些高级语法并不是说真的是花瓶,他们其实代表着你写代码需要考虑的一些问题,需要培养习惯,就像做算法题一样,有多少种解法,他们各自的复杂度怎么样?
  • 写代码培养了这些思维,才可以成为一个优秀的程序员!

[摘抄]C++异常处理的编程方法系列
不能在析构函数里面抛出异常:涉及到C++异常处理的理解
类型转换的三个层次
串讲:基本关键字、类型转换、运行时类型识别、重载运算、OOP、模板泛型

【安全】当我们需要考虑代码的安全性的时候,就要考虑下面的问题

什么是锁、互斥量、原子操作解决不了的并发问题实例
从C++和Java对比的角度理解并发编程的本质
C++并发编程-锁、互斥量、原子操作的区别与联系锁、互斥量、原子操作
并发编程实例-反例的改进实例
异常的概念+自带的语法功能异常

【解耦、复用】当我们需要考虑代码的逻辑的时候,就要考虑下面的问题

函数式编程的实现手段:函数对象类函数对象
模板:模板、泛型编程和静态多态模板

【易用性】

C++易用性改进

【效率】当我们需要考虑代码的执行效率的时候,据需要考虑以下这些知识点

从【移动构造】的意义,来学习值类别、值类型、生命周期、表达式类型移动构造
C++栈调用过程:C++自带功能
栈展开:C++自带功能
自己实现smart_ptr
自己实现unique_ptr
基础知识点1:堆、RALL、内存管理器

【内存、效率】从内存效率的角度,C++定义各种“虚”、各种规则,其实都是为了优化程序

【程序的正确性】从程序的正确性的角度

C++面试题之浅拷贝和深拷贝的区别

2020年3月15日 下午10:59

  1. 第一步:学习操作系统中基本概念。
    1. 这个过程中你纠结于局部的知识点,难将整体的操作系统各个部分、概念的关系串联起来。
    2. 学完就忘,是这个阶段的特点
  2. 第二步:将操作系统的概念,甚至连网络、虚拟化等知识都能联通在一起
    1. 这部分一定更要读刘超老师最后5讲的总结!
    2. [删]知识串讲:用一个创业故事串起操作系统原理1
    3. [删]知识串讲:用一个创业故事串起操作系统原理2
    4. [删]知识串讲:用一个创业故事串起操作系统原理3
    5. [删]知识串讲:用一个创业故事串起操作系统原理4
    6. [删]知识串讲:用一个创业故事串起操作系统原理5
  3. 第三步:动手实践
    1. 添加系统调用,并对内核进行Debug
    2. 学习制作操作系统:
      1. 《30天自制操作系统》日本人写的书,风格和《明朝那些事》的笔风类似,入门有趣,缺点可能是有点厚了,整体更像是一个代码说明。
      2. 《一个操作系统的实现》这本书没看过,现在也买不着了,但是天猫图书上有电子版。可以作为进阶内容

2020年3月15日 下午10:47

操作系统内核:也是存在底层调用的,并不是从零开始开发的!

  • BIOS就是操作系统的开发工具包。
  • 其实和你写leetcode是一样的,STL就像是BIOS提供的中断,面对很多问题,例如要调度优先级最高的任务,着其实就是各种算法。
  • 结论:像操作系统这样的底层、大程序,从思维难度上来说等价于LeetCode十几行得代码。
  • 算法题中的hard其实就是各种easy,medium的融合,这其中最重要的能力就是能够将问题转换为个个easy、medium的能力。这个hard不断的harder,再加上一些业务流程,其实就能不断的演化成操作系统一样的庞然大物。

linux操作系统的三种核心思想:

  1. 【类似于网络-上下级】自顶向下的层层调用,进行开发的思想
    • 自底向上的层层封装。
  2. 【模块化隔离-平级】
    • 操作系统再加一个黑盒调用,或者说是老板思维的任务外包。从上层视角理解理解文件系统,内存系统的起源就需要这样的角度
  3. 总结:其实这两种方法都是【隔离封装】,只不过一个侧重于上下级,一个侧重于平级之间。

再进一步,从学习操作系统的经验出发,对于一些新的企业级技术应该如何快速定位,抓住本质进行快速学习?

  • Kubernetes的核心思想就像操作系统发展过程中引入内存系统、文件系统一样,都是任务外包式的黑盒调用,起到了隔离的抽象作用,并且抽象的越来越厉害,网络、存储都可以使用Kubernetes进行抽象。这个感觉就计算机发展最最本质的一个趋势,十年前是这样,十年后依然不会变。