Git Product home page Git Product logo

blog's People

Contributors

v4if avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

单词翻转

局部reverse -> 整体reverse

#include <iostream>
#include <string>
#include <cassert>

using namespace std;
void reverse(string& str, int p, int q) {
    assert(p >= 0 && q >= 0 && p <= q && q < str.size());
    while (p < q) {
        swap(str[p], str[q]);
        p++;
        q--;
    }
}

void sentence_reverse(string& str) {
    int size = str.size();

    for (int i = 0; i < size; i++) {
        while(i < size && str[i] == ' ') i++;

        int word_begin = i;
        while (i < size && str[i] != ' ') i++;
        reverse(str, word_begin, i - 1);
    }
    reverse(str, 0, size - 1);
}

int main()
{
    string str{"I am a student."};
    sentence_reverse(str);
    cout << str << endl;
    cout << "Hello World" << endl;
    return 0;
}

Jump Game II - LeetCode

Given an array of non-negative integers, 
you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Your goal is to reach the last index in the minimum number of jumps.

For example:
Given array A = [2,3,1,1,4]
The minimum number of jumps to reach the last index is 2. 
(Jump 1 step from index 0 to 1, then 3 steps to the last index.)

递归

namespace jump_game2_backtracing {
class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.empty()) return 0;
        return recursive(nums, 0, 0);
    }

    int recursive(const vector<int>& nums, int n, int iter) {
        int len = nums.size();
        if (n == len - 1) return iter;
        if (n >= len) return INT32_MAX;

        int step = nums[n];
        if (step == 0) return INT32_MAX;

        vector<int> v;
        for (int i = 1; i <= step; ++i) {
            int result = recursive(nums, n + i, iter + 1);
            v.push_back(result);
        }
        return *min_element(v.begin(), v.end());
    }
};
}

动态规划

namespace jump_game2_dp {
    class Solution {
    public:
        int jump(vector<int>& nums) {
            int n = nums.size();

            if (n <= 1) return 0;

            vector<int> dp(n, 0);
            for (int i = 1; i < n; ++i) {
                int min = INT32_MAX;
                for (int j = 0; j < i; ++j) {
                    if ((j + nums[j] >= i) && (dp[j] + 1 < min)) {
                        min = dp[j] + 1;
                    }
                }
                dp[i] = min;
            }

            return dp[n - 1];
        }
    };
}

贪心

namespace jump_game2_greedy {
    class Solution {
    public:
        int jump(vector<int>& nums) {
            int n = nums.size();

            if (n <= 1) return 0;

            int last, curr, ret;
            last = curr = ret = 0;
            for (int i = 0; i < n; ++i) {
                if (i > last) {
                    last = curr;
                    ret++;
                }

                curr = max(curr, nums[i] + i);
            }

            return ret;
        }
    };
}

Inside the C++ object model 笔记

深度探索C++对象模型

关于对象

加上对象封装后的布局成本

struct -> class
data members直接内含在每一个class object之中,就像C struct的情况一样。而member functions虽然在class的声明之内,却不出现在object之中。每一个non-inline member function只会诞生一个函数实体。至于每一个"拥有零个或一个定义"的inline function则会在其每一个使用者(模块)身上产生一个函数实体。

C++在布局以及存取时间上主要的额外负担是由virtual引起,包括:

  1. virtual function机制:用以支持一个有效率的"执行期绑定"(runtime binding)
  2. virtual base class:用以实现"多次出现在继承体系中的base class,有一个单一而被共享的实体"

C++ 对象模式

在C++中,有两种class data members:static 和 nonstatic,以及三种class member functions:static、nonstatic和virtual

object

对此设计出的对象模型:

简单对象模型

在这个简单对象模型中,一个object是一系列的slots,每一个slot指向一个members。每一个data member或function member按其声明次序都有自己的一个slot
simple_model
在这个简单模型中,memebers本身并不放在object之中。只有指向member的指针才放在object内
Object中的members是以slot的索引值来寻址,因此一个class object的大小很容易计算出来:指针大小,乘以class中所声明的members数目便是

表格驱动对象模型

把所有与members相关的信息抽出来,放在一个data member table和一个member function table之中, class object本身则内含指向这两个表格的指针。member function table是一系列的slots,每一个slot指出一个member function,data member table则直接含有data本身
table_model

C++对象模型

nonstatic data members被配置于每一个class object之内,static data member则被存放在所有的class object之外。static和nonstatic function members也被放在所有的classobject之外。virtual functions则以两个步骤支持之:

  1. 每一个class产生出一堆指向virtual functions的指针,放在表格之中,这个表格被成为virtual table(vtbl)
  2. 每一个class object被添加了一个指针,指向相关的virtual table。通常这个指针被成为vptr,vptr的设定和重置都由每一个class的constructor、destructor和copy assignment运算符自动完成。每一个class所关联的type_info object(用以支持RTTI,运行时类型检查)
    也经由virtual table被指出来,通常是放在表格的第一个slot处
    object_model

关键词所带来的差异

C struct在C++中的一个合理用途,是当你要传递一个复杂的class object的全部或部分到某个C函数中去时,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。然而这项保证只在组合的情况下才存在,如果是继承则由编译器决定

对象的差异

由于多态的存在,在OO模型之中,程序员需要处理一个未知实体,它的类型虽然有所界定,却有无穷可能。这组类型受限于其继承体系,然而该体系理论上没有深度和广度的限制。原则上,被指定的object的真实类型在每一个特定执行点之前,是无法解析的。在C++中,只有通过pointers和references的操作才能够完成,因此对于ocject的多态操作要求此object必须可以经由一个pointer或reference来存取

// 没有多态(因为操作对象不是class object)
int *pi;

// 没有语言所支持的多态
void *pvi;

// ok : class x 视为一个base class(可以有多态的效果)
x *px;

这个共享接口是以virtual function机制引发的,它可以在执行期根据object的真正类型解析出到底是哪一个函数实体被调用

需要多少内存才能够表现一个class object

  1. 其nonstatic data members的总和大小
  2. 加上任何由于alignment(内存字节对齐,在32位的计算机上,通常alignment为4bytes即32位,使bus的运输量达到最高效率)
  3. 加上为了支持virtual而由内部产生的任何额外负担(vptr、bptr)

指针的类型

指向不同类型之各指针间的差异,在其所寻址出来的object类型不同,指针类型会告诉编译器如何解释某个特定地址中的内存内容及其大小
转型cast只是一种编译器指令,大部分情况下它并不改变一个指针所含的真正地址,只影响被指出之内存的大小和及其内容的解释方式

class ZooAnimal{
  public:
    ZooAnimal();
    virtual ~ZooAnimal();

    virtual void rotate();
  protected:
    int loc;
    string name;
};
ZooAnimal za("Zoey");
ZooAnimal *pza = &za;

zoo_mem_model
传统的string内存布局是8bytes,包括一个用来表示字符串长度的整数,一个4bytes的字符指针

继承内存布局一种实现方式:
class Bear : public ZooAnimal{};
derived_mem_model

Bear b;
ZooAnimal za = b; // 这会引起切割,vptr设定为指向基类的virtual table
// 调用ZooAnimal::rotate()
za.rotate();

构造函数语意学

默认构造函数 default constructor

如果default constructor已经被明确的定义出来,但没有初始化基类对象,编译器没办法合成第二个
编译器采取的行动是:如果class A内含一个或一个以上的member class objects,那么class A的每一个constructor必须调用每一个member classes的default constructor。编译器会扩张已存在的constrs,在其中安插一些代码,使得user code在被执行之前,先调用必要的default constructors

// 正确调用基类构造方法
Derived() : Base() {}

// 而不是
Derived() {Base()} // 基类构造方法会被调用两次,一次是编译器默认插入

拷贝构造函数 copy constructor

有三种情况会以一个object的内容作为另一个class object的初值:

  1. 明确的以一个object的内容作为另一个class object的初值 X xx = x;
  2. object被当作参数交给某个函数时 foo(xx);
  3. 当函数传回一个class object时 X foo_bar(){X xx; return xx;}

默认是以递归的方式施行成员拷贝,是否会被合成于程序之中取决于class是否会展现出拷贝构造语意,即是否是有用的实体

成员们的初始化队伍

下列情况中,为了让程序能够被顺利编译,必须使用member initialization list:

  1. 当初始化一个reference member时
  2. 当初始化一个const member时
  3. 当调用一个base class的constructor,而它拥有一组参数时
  4. 当调用一个member class的constructor,而它拥有一组参数时

list中的项目次序是由class中的member声明次序决定,而不是由initialization list中的排列顺序决定,且初始化操作被安插在explicit user code之前

对象语意学

class X{};
class Y : public virtual X{};
class Z : public virtual X{};
class A : public Y, public Z{};

// sizeof的结果视编译器而定,有没有empty virtual base class优化
/*
 sizeof(X) == 1 sizeof(Y) == 8 sizeof(Z) == 8 sizeof(A) == 12
 */
/*
 Visual C++ 5.0上执行结果
 sizeof(X) == 1 sizeof(Y) == 4 sizeof(Z) == 4 sizeof(A) == 8
 */

一个空的class,事实上并不是空的,它有一个隐晦的1byte,那是被编译器安插进去的一个char。这使得这个class的两个objects得以在内存中配置独一无二的地址

当语言支持virtual base classes时,就会导致一些额外负担。在derived class中,这个额外负担反映在某种形式的指针身上,它或者指向virtual base class subobject,或者指向一个相关表格,表格中存放的若不是virtual base subobject的地址,就是其偏移量

同时还需要考虑字节对齐的限制

X,Y,Z可能的一种对象布局
virtual_class_model

C++ Standard并不强制规定如base class subobjects的排列顺序不同存取层级的data members的排列顺序这种琐碎细节,也不规定virtual functions或virtual base classes的实现细节。那些细节由各家厂商自定

C++ 对象模型尽量以空间优化和存取速度优化的考虑来表现nonstatic data members,并且保持和C语言struct数据配置的兼容性。它把数据直接存放在每一个class object之中。对于继承而来的nonstatic data members(不管是virtual或nonvirtual base class)也是如此。不过并没有强制定义其间的排列顺序。至于static data members,则被放置在程序的一个global data segment中,不会影响个别的class object的大小。在程序之中,不管该class被产生出多少个objects(经由直接产生或间接派生),static data members永远只存在一份实体(甚至即使该class没有任何实体,其static data members也已存在)

Data Member 的布局

C++ Standard 要求,在同一个access section(也就是private、public、protected等区段中),members的排列只需要符合较晚出现的memebers在class object中有较高的地址这一条件即可,也就是说,各个memebers并不一定得连续排列

什么东西可能会介于被声明的memebers之间呢?

  1. memebers的内存补齐可能就需要填补一些bytes
  2. 编译器还可能合成一些内部使用的data memebers,以支持整个对象模型,vptr就是这样的东西,当前所有的编译器都把它安插在每一个内含virtual function 之 class的object内。而vptr会被放在什么位置是不确定的。传统上它被放在所有明确声明的memebers的最后。不过也有一些编译器把vptr放在一个class object的最前端。C++ Standard秉持之前说的对于布局所持的放任态度,允许编译器把那些内部产生出来的memebers自由放在任何位置上,甚至放在那些被程序员声明出来的memebers之间

Static Data Memebers

如果有两个classes,每一个都声明了一个static memeber freelist,那么它们都被放在程序的data segment时,就会导致命名冲突。编译器的解决方法是暗中对每一个static data memeber编码(name-mangling),以获得一个独一无二的程序识别代码。但编译器之间的做法不同

Nonstatic Data Members

Nonstatic Data Members直接存放在每一个class object之中,欲对一个nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data member的偏移量(offset)

Point3d *pt3d;
pt3d->_x = 0.0;

其执行效率在_x是一个struct member、一个class member、单一继承、多重继承的情况下都完全相同。单如果_x是一个virtual base class的member,存取速度会比较慢一点,虚拟继承将为经由base class subobject存取class members导入一层新的间接性

继承与Data Member

C++语言保证出现在derived class中的base class subobject有其完整原样性,由于字节对齐的存在,可能因为继承而膨胀所需的空间
一般而言,边界调整(alignment)是由处理器来决定的

2017-05-31_13-54-13

Vertex3d *pv3d;
Vertex *pv;

pv = pv3d;
// C++内部转换操作
pv = pv3d ? (Vertex*)(((char*)pv3d) + sizeof(Point3d));

要存取第二个或后继base class中的一个data member,不需要付出额外的成本。members的位置在编译时就固定了,因此存取members只是一个简单的offset运算,就像单一继承一样,不管是经由一个指针、一个reference或是一个object来存取

Function 语意学

C++支持三种类型的member functions:static、nonstatic、virtual,每一种类型被调用的方式都不相同

Nonstatic Member Functions 非静态成员函数

实际上member function会被内化为nonmember的形式:

  1. 改写函数原型以安插一个额外的参数到member function中,用以提供一个存取管道,使class object得以调用该函数。该额外参数被称为this指针
float Point2d::test(){}

// 增长过程
float Point2d::test(Point2d *const this)

// 如果memeber function 是 const
float Point2d::test(const Point2d *const this)
  1. 将每一个对nonstatic data memebers的存取操作改为经由this指针来存取
{
  return sqrt(this->x_ * this->x_ + this->y_ * this->y_);
}
  1. 将memeber function重新写成一个外部函数。对函数名进行mangling(矫正)处理,使得在程序中称为独一无二的词汇
    member名称、class名称、参数链表 -> 内部名称
extern test_7Point2dFv(const Point2d *const this);

每一个调用操作也由编译器进行转换

obj.test();
test_7Point2dFv(&obj);

ptr->test();
test_7Point2dFv(ptr);

nonmember、static member或nonstatic member函数都会被转化为完全相同的形式

Static Member Functions 静态成员函数

obj.normalize();

normalize_7Point3dSFv();
// 在C++引入静态成员函数之前,很少看到下面这种怪异写法
// 把0强制转换成一个class指针,因而提供出一个this指针实体
((Point3d*)0)->object_count();

Point3d::object_count();

没有任何一个memebers被直接存取,事实上就不需要this指针,因此也就没有必要通过一个class object来调用一个member function

如果取一个static member function的地址,获得的将是其在内存中的位置,也就是其地址。
也就是说&Point3d::object_count();会得到一个数值,类型是unsigned int (*)();

Virtual Memeber Functions 虚拟成员函数

单继承多态
2017-06-01_09-23-23
VIRTUAL_RETRY

ptr->z();
(*ptr->vptr[4])(ptr);
如何在编译时期设定virtual function的调用:

  1. 一般而言,并不知道ptr所指对象的真正类型。然而确定的是经由ptr可以存取到该对象的virtual table
  2. 虽然不知道哪一个z()函数实体会被调用,但每一个z()函数地址都被放在slot4

唯一在执行期才能知道的东西是:slot4所指的到底是哪一个z()函数实体

多重继承下的 Virtual Functions

2017-06-01_10-35-03
在多重继承之下,一个derived class内含n - 1个额外的virtual tables,n表示其上一层base classes的数目(单一继承将不会有额外的virtual tables)

用以支持一个class拥有多个virtual tables的传统做法是,将每一个tables以外部对象的形式产生出来,并给予独一无二的名称。因此Derived所关联的两个tables可能有这样的名称:
vtbl_Derived;
vtbl_Base2_Derived;

Virtual Table 的布局:多重继承情况
2017-06-01_10-40-51

以下情况,第二或后继的base class会影响对virtual functions的支持:

  1. 通过一个指向第二个base class的指针,调用derived class virtual function
Base2 *ptr = new Derived;

// 调用 Derived::~Derived
// ptr 必须向后调整sizeof(Base1)个bytes
delete ptr;
  1. 通过一个指向derived class的指针,调用第二个base class中一个继承而来的virtual function,在此情况下,derived class指针必须调整,以指向第二个base subobject
Derived *pder = new Derived;

// 调用Base2::mumble()
// pder必须被向前调整sizeof(Base1)个bytes
pder->mumble();

支持指向virtual member function之指针

对一个nonstatic member function取其地址,将获得该函数在内存中的地址,然而面对一个virtual function,其地址在编译时期是未知的,所能知道的仅是virtual function在其相关virtual table中的索引值,也就是说,对一个virtual member function取其地址,所得到的只是一个索引值

对于指向memeber function的指针 float(Point::*pmf)();,必须允许该函数能够寻址出virtual function和nonvirtual function。因此其数值可以被区别代表内存地址还是virtual table中的索引值

在cfront2.0非正式版中,采取的技巧如下:

(((int) pmf ) & ~127) 
? // nonvirtual
(*pmf)(ptr)
: // virtual
(*ptr->vptr[(int)pmf](ptr));

这种实现技巧必须假设继承体系中最多只有128个virtual functions,多重继承的引入导致需要更一般化的实现方式

构造、结构、拷贝语意学

Point *heap = new Point;

// C++伪码
Point *heap = __new(sizeof(Point));
if(heap!=0)
  heap->Point::Point();


// 与malloc的区别
Point *heap = static_cast<Point*>(malloc(sizeof(Poing)));
// 定位new
new (heap) Point;

// 析构
heap->Point::~Point();
// 该操作并不会导致destructor被调用,并没有明确的提供一个destrctor实体
delete heap;

针对数组的new语意

int *p_array = new int[5];
int *p_array = (int*)__new(5 * sizeof(int));

template
当编译器看到template class声明时,在实际程序中什么反应都没有,除非将template具现出来
只能通过template Point class的某个实体来存取或操作

// ok 
Point<float>::Status s;
// error
Point::Status s;

推荐一个C++在线编译器Wandbox

Wandbox
支持boost,gcc多种版本可选,以及一些其他的语言如GO,Python

2018-01-15_09-54-59
原样式界面编辑窗口字体偏小,输出窗口颜色感觉刺眼,因此做了一些调整

安装Stylish插件

2018-01-15_09-58-10

为Wandbox添加样式

/* 代码编辑区字体调整 */
.wandbox-smart-editor .CodeMirror pre {
    font-family: 'Yahei monaco', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Courier New', monospace;
    font-size: 16px;
    line-height: 16px;
}

/* 隐藏左侧边栏 */
.visible-md-block.visible-lg-block{
	display: none !important;
}

/* 输出框 */
.output-window {
    margin: 0px;
    background-color: #f7f7f7;
    overflow-y: scroll;
}
.output-window pre {
    margin: 0px 0px 10px;
    padding: 0px;
    background-color: #f7f7f7;
    color: black !important;
    border: 0px;
    font-family: 'Yahei monaco', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'Courier New', monospace;
}
.permlink {
    background-color: #f7f7f7;
}

/* 运行时选项 */
.wandbox-hidden-runtime-option-raw {
    display: inline;
}
.wandbox-shown-runtime-option-raw{
	display: none;
}

/* Start Finish */
.output-window .Control {
    color: #170 !important;
}
/* return 0 */
.output-window .ExitCode {
    color: #a11 !important;
}

Applies to URL
https://wandbox.org/
https://wandbox.org/#
default

2018-01-15_09-50-26

清爽干净了好多

对这次CPU漏洞Meltdown和Spectre的一些理解

569476-meltdown-spectre-exploit-3

熔断和幽灵

  • 对于Meltdown原因是在Intel 的投机执行Speculative execution,即乱序处理器会在某个指令在等待的时候,预先开始执行之后的指令,然后通过speculation的机制来保证如果预先执行了不该执行的指令,不会产生体系结构层面可见的结果,把执行结果discard掉,但是不会flush更新的缓存,而且在投机执行的时候Intel的CPU没有进行权限判断,为了提高执行效率,而是选择在commit的时候进行页表访存地址需要的权限进行检查,对权限不足的结果discard掉,因此即使在commit的时候由于访存权限被discard掉,仍然会留下缓存更新的痕迹

  • time based attack:时间旁路攻击,即数据被缓存之后的访问时间与直接从内存load的时间差异大,可以通过访问时间判断该page是否被缓存

  • 现代OS虚拟内存在设计的时候应用程序和内核空间在同一虚拟地址空间,一般将3G以上内核虚拟空间直接线性映射到物理内存空间,为了缓解内核漏洞利用出现了KALSR内核地址随机化机制,但还是共享同一虚拟地址空间,应用程序对内核空间访问的限制是硬件根据页表访存地址权限进行禁止

到这里可能会有疑问,只是执行了不该执行的指令,并且对结果已经discard掉了,会产生什么影响。

这里还有一个预执行产生的后果没有消除掉,那就是预执行过程中对缓存的更新,而且前面说了一个前提条件,也就是Intel的芯片为了更多的预支处理器的性能,在对后续指令进行投机执行的时候没有进行权限检查!

也就是说低权限级别即用户程序可以在投机执行的时候对内核的虚拟地址进行访存!但是访存之后怎么把结果带出来,在commit的时候会对权限进行检查,如果不符合就会抛异常,然后把结果discard掉,这里巧妙的利用了不会flush掉的缓存信息然后进行time based attack旁路攻击。

用户态程序可以开辟一段256个page大小的内存,用来做probe array探测数组,并且保证没被cache过。假设我们要读取kernel_vm_addr中的内容,可以先读取kernel_vm_addr的第一个byte,这条指令肯定会产生异常,但是由于投机执行,后面的指令仍会执行,把读到的第一个byte_data * 4096作为索引去访问probe array,即probe_array[byte_data * 4096],这会导致该地址的页块被cache住,而且访存指令异常后不会flush掉,因此可以通过测量对256个page的内存访问时间得到第一个byte的数据内容,以此类推。

给出的应对方案是KPTI/KAISER:把内核从程序的虚拟地址空间里移出去,只保留最最最基本的东西,需要用的时候再切换回来,在切换的时候会造成TLB的刷新

  • 对于Spectre是由于CPU对指令的分支预测执行造成的,对Intel、AMD、ARM均有影响,分支预测时也没有权限检查的概念

参考的地方:
bnm zasdfg - 如何看待 2018 年 1 月 2 日爆出的 Intel CPU 设计漏洞
某琳 - 如何看待 2018 年 1 月 2 日爆出的 Intel CPU 设计漏洞

缓冲区设计思路

Buffers

数据传输效率不对称,或提前不知道接收数据的大小都需要进行缓冲区的设计,而对于TCP本身就是一个流协议,一次发送可能多次接收,一次接收也可能是多次堆积的数据,因此对于消息接收解析缓冲区的设计必不可少,用streambuf从async_read_some读取数据,然后再对streambuf里面的数据进行消费,通过split_and_process_message将streambuf里面完整的消息体取出

buffers又可以简单的表示成一个包含pointer和size的tuple,比如boost里面

typedef std::pair<void*, std::size_t> mutable_buffer;
typedef std::pair<const void*, std::size_t> const_buffer;

底部存储容器可以使用array、vector、string等

buffers的设计离不开内存的管理,一种方案是固定内存块大小,配合内存池,当缓冲块底层存储容量被消耗完了之后,将缓冲块释放到内存池,然后重新分配一个缓冲块;还有一种方案是对当前缓冲块进行重用,但是需要对没有consume的数据进行整理,调整到缓冲块的头部,asio::streambuf就是采取的这种方式

asio::streambuf

asio::streambuf则是提供了一个流类型的buffer,它自身是能申请内存的,包含数据内容,而且内存可以自动增长。它的好处是可以通过stl的stream相关函数实现缓冲区操作,处理起来更加方便

而asio::buffer只是一个适配器,使用时需要注意原始数据源

streambuf

put get

对asio::streambuf的简单测试

size_t fake_read(boost::asio::streambuf::mutable_buffers_type buff) {
    size_t size_ = boost::asio::buffer_size(buff);

    srand(time(NULL));
    size_t max_size_ = rand() % size_;
    std::cout << "prepare out sequence size_ : " << size_ 
        << "\t random read size_ : " << max_size_ << std::endl;
    char* p = boost::asio::buffer_cast<char*>(buff);
    for (size_t i = 0;i < max_size_;i++) {
        p[i] = (i % 10) + 0x30;
    }

    return max_size_;
}

void stream_buff_test() {
    boost::asio::streambuf sb;
    auto prev = sb.data();
    std::ostream os(&sb);
    os << "Hello world\n";
    auto next = sb.data();

    // true
    std::cout << (boost::asio::buffers_begin(prev) 
        == boost::asio::buffers_begin(next)) << std::endl; 
    // false 说明输出序列的写指针pptr向后移动了
    std::cout << (boost::asio::buffers_end(prev) 
        == boost::asio::buffers_end(next)) << std::endl;

    std::cout << sb.size() << std::endl;
    // 移动输入序列读指针gptr,并调整尾指针egptr为pptr
    sb.consume(sb.size());
    std::cout << sb.size() << std::endl;

    std::cout << sb.max_size() << std::endl;

    /*
    主要调用reverse进行内存处理
    if epptr - pptr 空间够用,则直接返回
    else {
        将未读取的输入序列的内容移动到缓冲区头部
        buffer_.resize 底层存储申请内存
        setg 调整输入序列指针
        setp 调整输出序列指针
    }
    */
    boost::asio::streambuf::mutable_buffers_type out_bufs = sb.prepare(512);
    std::cout << sb.size() << std::endl;
    size_t bytes_transferred = fake_read(out_bufs);
    // 移动输出序列写指针
    sb.commit(bytes_transferred);
    std::cout << sb.size() << std::endl;

    boost::asio::streambuf::const_buffers_type in_bufs = sb.data();
    std::string data(boost::asio::buffers_begin(in_bufs), 
        boost::asio::buffers_begin(in_bufs) + bytes_transferred);
    std::cout << data << std::endl;

    sb.consume(bytes_transferred);
    std::cout << sb.size() << std::endl;
}

asio::streambuf需要注意的一些东西

  • streambuf的input sequence输入序列和output sequence输出序列,在底层共用同一个存储容器buffer_,只是通过读写指针控制两个名义上的序列
  • data()和prepare()都是返回的子buffer,需要自行处理读写指针。data()消费数据后需要调用consume()移动读指针,prepare()填充数据后需要调用commit()移动写指针
  • streambuf一旦被撑大了,就不会再被缩小,不会进行shrink-to-fit
  • 如果把整个buffer都传递到一个自由函数中,比如read_until(sock, buffer, "\n");方法会保证把buffer的输入输出指针指向的位置进行增加;但是如果使用子buffer作为参数,需要手动修改读写指针,比如read(sock, buf.prepare(16), transfer_exactly(16) ); 需要自己进行移动写指针buf.commit(16)
    streambuf的使用原则,当把streambuf对象作为一个整体进行IO操作时,应用程序无需关心输入输出指针
  • 在prepare和commit之间不要调用对输入或者输出序列有更改的操作,比如std::ostream
// Prepare 1024 bytes for the output sequence.  The input sequence is
// empty.
boost::asio::streambuf streambuf;
streambuf.prepare(1024);

// prepare() and write to the output sequence, then commit the written
// data to the input sequence.  The API contract has been violated.
std::ostream ostream(&streambuf);
ostream << "1234567890";

// Commit 10 unspecified bytes to the input sequence.  Undefined
// behavior is invoked.
streambuf.commit(10);
  • buf相对于流来说是输入序列
boost::asio::streambuf buf;
std::ostream os(&buf);
os << "Hello world\n";

C++ Primer Plus 笔记

void

int main(void) // very explicit style 明确
在括号中使用关键字void明确的指出,函数不接受任何参数。在c++中(不是C),让括号空着与在括号中使用void等效,在C中让括号空着意味着对是否接受参数保持沉默。

cout

using std::cout; // make cout available
cout << "Hello world" << endl;  // cout对象  插入运算符
  // << 运算符重载  | 左移
  // 同一个符号可以有多种含义,而编译器根据上下文来确定其含义

endl确保程序继续运行前刷新输出(将其立即显示在屏幕上),而使用\n不能提供这样的保证

C++提供了两种发送消息的方式:一种是使用类方法(函数调用);另外一种方式是重新定义运算符,cin和cout采用的就是这种方式。

变量

integer_overflow

int chest = 42; // 基数为10
int waist = 0x42; // 基数为16
int inseam = 042; // 基数为8

cout << 1UL; // 被存储为unsigned long

如果之前学习过C语言,并打算使用#define来定义符号常量,请不要这样做,而应使用const

1e9 = 1 * 1000000000

数组

编译器不会检查使用的下标是否有效

char stack[10];
// index最大为9
stack[10] = 'z';
cout << stack[10] << endl;

int totals[50] = {0}

字符串

C风格字符串:以空字符null结尾,空字符被写作\0,其ASCII码为0,用来标记字符串结尾

char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // not a string!
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // a string
char bird[11] = "Mr. Cheeps"; // the \0 is understood

strlen("hello");

sizeof运算符指出整个数组的长度,单strlen返回的是存储在数组中的字符串长度,\0结束?

string对象:表示字符串的实体

string str1;
string str2 = "panther";
string str3;

str3 = str1 + str2;
str1 = str2;

指针

面向对象编程与传统过程性编程的区别在于,OOP强调的是在运行阶段(而不是编译阶段)进行决策。
C++采用的方法是,使用关键字new请求正确数量的内存以及使用指针来跟踪新分配的内存的位置。

int *pn = new int;
delete pn;

// 对空指针使用delete是安全的
char *p = nullptr;
delete p;

int *pa = new int[10];
// 方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素
delete [] pa;

int tell[10];
// 数组的名称是第一个元素的地址
cout << tell << endl; // &tell[0]
cout << &tell << endl; // int (*p)[10]  (*p)[0]

对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。

数组、array、vector

自动存储:局部变量、栈内存
静态存储:static double fee = 56.50
动态存储:new、delete

array对象和数组存储在相同的内存区域(即栈)中,而vector对象存储在自由存储区域或堆中。

类型别名

#define BYTE char
typedef char byte;

函数

函数通过将返回值复制到指定的CPU寄存器或内存单元中来将其返回
函数原型描述了函数到编译器的接口,它将函数返回值的类型以及参数的类型和数量告诉编译器

指针和const

const int *pt; // 指针指向一个常量对象,防止使用该指针修改所指向的值
int * const pt; // 将指针本身声明为常量,防止改变指针指向的位置

函数和二维数组

// 二维数组作为参数的函数,数组名被视为其地址,相应的形参是一个指针
int fun(int (*arr)[10], size_t n);
int fun(int [][10], size_t n);
// 也可以传递两个指针,其中一个指向数组开头,另一个指向数组末尾的下一个元素,以指定一个范围

函数探幽

内联函数

要使用这项特性,必须采取下述措施之一:

  1. 在函数声明钱加上关键字inline;
  2. 在函数定以前加上关键字inline;
    通常的做法是省略原型,将整个定义放在本应提供原型的地方
    程序员请求将函数作为内联函数时,编译器并不一定会满足这种要求。它可能认为该函数过大或者函数调用了自己(内联函数不能递归),因此不能将其作为内联函数。

C语言使用预处理器语句#define来提供宏--内联代码的原始实现,但宏不能按值传递,而是通过文本替换来实现的。

引用变量

int & 指的是指向int的引用,必须在声明引用时将其初始化
返回引用时应避免返回函数终止时不再存在的内存单元引用,为避免这种问题,最简单的方法是,返回一个作为参数传递给函数的引用。另一种方法是用new来分配新的存储空间。

如果对象是数组,则使用指针,因为这是唯一的选择,不存在数组引用?

默认参数

char *left(const char *str, int n = 1);

// 返回一个指针,指向字符串前n个字符
char *left(const char *str, int n) {
  if (n < 0) n = 0;
  char *p = new char[n + 1];
  int i;
  for (i = 0; i < n && str[i]; i++) {
    p[i] = str[i];
  }

  while (i <= n) {
    p[i++] = '\0';
  }

  return p;
}

函数重载

函数重载的关键是函数的参数列表

void sink(double & r1); // lvalue
void sink(const double & r2); // const
void sink(const && r3); // rvalue

函数模板

// typename OR class
template <typename T>
void swap(T &a, T &b) {
  T temp;
  temp = a;
  a = b;
  b = temp;
}

swap<int>(a, b);

函数模板不能缩短可执行程序,最终仍将由独立的函数定义,最终的代码不包含任何模板,只包含了为程序生成的实际函数。

decltype(C++11)

int x;
decltype(x) y; // make y the same type as x

后置返回类型

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x + y)
{
  return x + y;
}

内存模型和名称空间

说明符和限定符

c-v限定符
关键字volatile表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。例如,可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间和信息。将变量声明为volatile,相当于告诉编译器,不要进行这种缓存到寄存器中的优化。

mutable
用它来指出,即使结构(或类)变量为const,其某个成员也可以被修改。

语言链接性

extern "C" void spiff(int); 使用c语言链接性 函数名翻译可能不一样

定位new运算符

能够指定要使用的位置,要使用new特性,首先需要包含头文件new,它提供了new运算符的原型

int * p1 = new int; // invoke new(sizeof(int))
int * p2 = new(buffer) int; // invoke new(sizeof(int), buffer)

定位new运算符用于对象时,需要显示的调用析构函数

char * buff = new char[512];
classname *pc = new (buff) classname;
pc->~classname(); // 显示调用析构
delete[] buff;

名称空间

// using 声明
using std::cout;
// using 编译
using namespace std;

对象和类

定义位于类声明中的函数都将自动成为内联函数,因此Stock::set_tot()是一个内联函数
也可以在类声明之外定义成员函数,并使其成为内联函数

class Stock{
  private:
    void set_tot();
};

// 不仅是一个void类型的函数,而是一个术语Stock类的void函数
inline void Stock::set_tot() {}

// const成员函数,不修改调用对象
void Stock::show() const {}

类对象各自有自己的数据,但是共享一组成员函数

由于this指针被设置为调用对象的地址,因此*this是该对象的别名

如果希望派生类可以重新定义基类的方法,则可以使用关键字virtual将它声明为虚的,这样对于通过指针或引用访问的对象,能过根据对象类型来处理,而不是根据引用或指针的类型来处理,即指针所指的对象类型。

使用public建立is-a关系,使用包含或者私有继承来建立has-a关系,更倾向于包含

纯虚方法

vritual double area() const = 0;
可以在声明中的分号前面加上=0来声明纯虚方法
不一定非得定义纯虚方法,对于包含纯虚成员的类,不能使用它来创建对象。纯虚方法用于定义派生类的通用借口。

构造函数与析构函数

当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数

如果构造函数使用new来分配内存,则析构函数使用delete来释放内存

重载

// 成员函数重载
Time Time::operator*(double mult) const {}

A = B * 2.75; // 转换为A = B.operator*(2.75)函数调用
// 不能处理
A = 2.75 * B;
// 非成员函数重载运算符
Time operator*(double m, const Time &t){}
// 可以按成员函数相反顺序处理操作数, 不能访问类的私有成员
// A = operator*(2.75, B);

成员函数可以访问私有数据

重载运算符:作为成员函数还是非成员函数,不能同时选择这两种格式
一般来说非成员函数应是友元函数,这样它才能直接访问类的私有数据
对于成员函数版本来说,一个操作数通过this指针隐式传递,另一个操作数作为函数参数显示传递;对于友元版本来说,两个操作数都作为参数来传递。

友元函数

只有类声明可以决定哪一个函数是友元
将原型放在类声明中,并在原型声明前加上关键字friend
friend Time operator*(double m, const Time & t);
定义中不要使用friend
Time operator*(double m, const Time & t);

该原型意味着两点:

  1. 虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符调用
  2. 虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同

使用友元函数重载<<

ostream & operator<<(ostream & os, const Time & t) {
  os << t.hours << " hours, " << t.minutes << " minutes";
  return os;
}

cout << "Trip time: " << trip << endl;

类内enum

定义类特定的常量
static const int Lbs_per_stn = 14;
OR

class Vector {
  enum Mode {RECT, POL};
};

Vector::RECT;

随机

srand(time(0));

转换函数

// 转换为double类型的函数原型
operator double();

注意以下几点:

  1. 转换函数必须是类方法
  2. 转换函数不能指定返回类型
  3. 转换函数不能有参数

类和动态内存分配

class string {
  private:
    char *str;
    int len;
  public:
    string() {
      len = 0;
      str = new char[1];
      str[0] = '\0';
    }
    string(const char *s) {
      len = std::strlen(s);
      // 包含末尾的空字符
      str = new char[len + 1];
      strcpy(str, s);
    }

    // 复制构造函数
    string(const string & st) {
      len = st.len;
      str = new char[len + 1];
      strcpy(str, st.str);
    }

    // 赋值运算符
    string & operator=(const string & st) {
      // 自我赋值
      if (this == &st) return *this;

      delete [] str; // 删除旧str
      len = st.len;
      str = new char[len + 1];
      strcpy(str, st.str);

      return *this;
    }

    ~string() {
      delete[] str;
    }

    // 友元
    friend std::ostream & operator<<(std::ostream & os, const string & st);
};

std::ostream & operator<<(std::ostream & os, const string & st) {
  os << st.str;
}

如果类中使用了new初始化的指针成员,应当定义显示复制构造函数和执行深度复制的赋值运算符
或者将所需的方法定义为私有

class string {
  private: 
    string(const string & st){}
};

// 不允许
string str2(str1);
str2 = str1;
  1. 避免了自动生成的默认方法定义
  2. 阻止了复制行为

当对象被按值传递或返回时,复制构造函数将被调用

静态成员变量初始化

class Test{
  static int O_var;
};

int Test::O_var = 0;

不能在类声明中初始化静态成员变量,声明描述了如何分配内存,但并不分配内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。初始化语句指出了类型,并使用了作用域运算符,但没有使用关键字static
一种例外情况是静态数据成员为整型或枚举型const

特殊成员函数

C++自动提供了下面这些成员函数:

  1. 默认构造函数,如果没有定义构造函数
  2. 默认析构函数,如果没有定义
  3. 复制构造函数,如果没有定义(默认的复制构造函数逐个复制非静态成员,复制的是成员的值,浅复制)
  4. 赋值运算符,如果没有定义(赋值运算符是只能由类成员函数重载的运算符之一)
  5. 地址运算符,如果没有定义

C++11还提供了另外两个特殊成员函数:移动构造函数(move constructor)和移动赋值运算符(move assignment operator)

string str1("test");
string str2 = str1; // 初始化,复制构造函数

str2 = str1; // = 赋值运算符

C++11空指针

nullptr

有关返回对象的说明

Vector max(const Vector &v1, const Vector & v2);
const Vector & max(const Vector &v1, const Vector & v2);

返回对象将调用复制构造函数,而返回引用不会,引用指向的对象应该在调用函数执行时存在

派生类

除非使用默认构造函数,否则应显示调用正确的基类构造函数
创建派生类对象时,首先调用基类构造函数,然后调用派生类构造函数。释放对象的顺序相反,首先执行派生类的析构函数,然后自动调用基类的析构函数

基类指针可以在不进行显示类型转换的情况下指向派生类对象,基类指针或引用只能调用基类方法

显示调用基类赋值运算符
baseClass::operator=(lv)

派生类中的友元函数

std::ostream & operator<<(std::ostream & os, const hasDMA &hs) {
  // type cast to match operator<<(std::ostream & os, const baseDMA &)
  os << (const baseDMA &)hs;
  os << "Style: " << hs.style << ednl;
  return os;
}

基类的析构函数应当是虚的

复制构造函数

在下述情况下,将使用复制构造函数:

  1. 将新对象初始化为一个同类对象
  2. 按值将对象传递给函数
  3. 函数按值返回对象
  4. 编译器生成临时对象

按值传递对象与传递引用

编写使用对象作为参数的函数时,应按引用而不是按值来传递对象。这样做的原因之一就是提高效率。按值传递对象涉及到生成临时拷贝,即调用复制构造函数,然后调用析构函数。如果函数不修改对象,应将参数声明为const引用。

使用using重新定义访问权限

使用保护派生或私有派生时,基类的公有成员将成为保护成员或私有成员。假设要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。另一种方法是使用一个using声明(就像名称空间那样)来指出派生类可以使用特定的基类成员,using声明只适用于继承,而不适用于包含
using std::valarray<double>::min;

多重继承

多重继承,尤其是菱形继承面临的两个主要问题:从两个不同的基类继承同名方法;从两个或更多相关基类那里继承同一个类的多个实例。

虚基类
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象

// 当派生类使用关键字virtual来指示派生时,基类就成为虚基类
class Singer : virtual public Worker {};
class Waiter : public virtual Worker {};
class SinngingWaiter : public Singer, public Waiter {};
// SinngingWaiter对象将只包含Worker对象的一个副本

类模板

模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数
不能将模板成员函数放在独立的实现文件中,由于模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。最简单的办法就是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。

RTTI

运行阶段类型识别

类型转换描述符

dynamic_cast
const_cast
static_cast
reinterpret_cast

智能指针模板类

当函数终止时,不管是正常终止,还是出现了异常终止,本地变量都将从栈内存中删除——因此指针ptr占据的内存将被释放,但是所指向的内存并没有被释放。

问题在于ptr只是一个常规指针,不是有析构函数的对象。如果是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr、unique_ptr、shared_ptr背后的**。

模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案unique_ptr和shared_ptr

weak_ptr?

#include <iostream>
using namespace std;

template <typename T>
class smart_ptr
{
private:
  T *ptr; // 智能指针底层保管的指针
  int *count; //引用计数
public:
  smart_ptr(T*); // 用普通指针初始化智能指针
  smart_ptr(smart_ptr&); // 拷贝构造函数

  T* operator->(); // 自定义指针运算符
  T& operator*(); // 自定义解引用运算符
  smart_ptr& operator=(smart_ptr&); // 自定义赋值运算符

  ~smart_ptr(); // 自定义析构函数
};

template <typename T>
smart_ptr<T>::smart_ptr(T *p): count(new int(1)), ptr(p) {}

template <typename T>
//对普通指针进行拷贝,同时引用计数器加1,因为需要对参数进行修改,所以没有将参数声明为const
smart_ptr<T>::smart_ptr(smart_ptr &p): count(&(++*p.count)), ptr(p.ptr)  {}

template <typename T>
T* smart_ptr<T>::operator->() {
    return ptr;
}

template <typename T>
T& smart_ptr<T>::operator*() {
    return *ptr;
}

// 左边指针不再指向原对象,考虑内存释放
// 定义赋值运算符,左边的指针计数减1,右边指针计数加1,当左边指针计数为0时,释放内存
template <typename T>
smart_ptr<T>& smart_ptr<T>::operator=(smart_ptr& sp) {
    ++*sp.count;
    if (--*count == 0) { //自我赋值同样能保持正确
        delete count;
        delete ptr;
    }
    this->ptr = sp.ptr;
    this->count = sp.count;
    return *this;
}

template <typename T>
smart_ptr<T>::~smart_ptr() {
    if (--*count == 0) {
        delete count;
        delete ptr;
    }
}

// To compile: g++ -O -g -fsanitize=address use-after-free.c
int main(int argc, char **argv)
{
  smart_ptr<int> p(new int(9));
  cout << *p << endl;
  return 0;
}

有关智能指针注意的事项

// 为何摒弃auto_ptr
auto_ptr<string> ps(new string("auto_ptr test"));
auto_ptr<string> vocation;
vocation = ps;
// vocatoin将剥夺ps的所有权,ps不再指向有效的数据

unique_ptr<string> p3(new string("unique_ptr test"));
unique_ptr<string> p4;
p4 = p3; // 非法,编译器将阻止

如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。
避免这种问题,可以采取以下方法:

  1. 定义赋值运算符,使之进行深复制。这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本
  2. 建立所有权概念,对于特定的对象,只能有一个智能指针可拥有它,这样就只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格
  3. 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。仅当最后一个指针过期时,才调用delete,这是shared_ptr采用的策略

move

仅当以非智能的方式使用遗弃的智能指针(如解除引用时),这种赋值才不安全。要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),能够将一个unique_ptr赋给另一个
p4 = std::move(p3); // 使用了C++11新增的移动构造函数和右值引用

标准模板库STL

所有STL容器都提供了一些基本方法,其中包括size()、swap()、begin()——返回一个指向容器中第一个元素的迭代器、end()——返回一个超过容器尾的一个容器,这与C风格字符串最后一个字符后面的空字符类似,超尾元素

push_back()、erase()、insert()

STL从更广泛的角度定义了非成员函数来执行搜索、排序、随机排序等操作,即不是为每个容器类定义find()成员函数,而是定义了一个适用于所有容器类的非成员函数find()。这种设计理念省去了大量重复的工作

容器种类:deque、list、queue、priority_queue、stack、vector、map、multimap、set、multiset、bitset
C++11新增了forward_list、unordered_map、unordered_multimap、unordered_set、unordered_multiset

泛型编程

面向对象编程关注的是编程的数据方面,而泛型编程关注的是算法。泛型编程旨在编写独立于数据类型的代码。
在C++中完成通用程序的工具是模板

缓冲区

像磁盘驱动器这样的设备以512字节(或更多)的块为单位来传输信息,而程序通常每次只能处理一个字节的信息。缓冲区帮助匹配这两种不同的信息传输速率。
buff_stream
键盘输入每次提供一个字符,因此在这种情况下,程序无需提供缓冲区来帮助匹配不同的数据传输率。然而,对键盘输入进行缓冲可以让用户在将输入传输给程序之前返回并更正

UNP笔记

Unix Network Programming

TCP/IP协议族

数据链路:1500字节以太网的MTU大小
|
网络层:IPv4 IPv6
|
传输层:TCP UDP [TCP和UDP之间留有间隙,表明网络应用绕过传输层直接使用IPv4或IPv6是可能的,就是所谓的原始套接字]
|
应用层

#include <stdio.h>
int main()
{
  printf("%d\r\n", sizeof(int));
  printf("%d\r\n", sizeof(int *));

  return 0;
}

> 4
> 8

TCP与UDP的区别

每个UDP数据报都有一个长度。如果一个数据报正确的到达其目的地,那么该数据报的长度将随数据一道传递给接收端应用进程。而TCP是一个字节流(byte-stream)协议,没有任何记录边界

UDP提供无连接的服务,UDP客户与服务器之间不必存在任何长期的关系。一个UDP服务器可以用同一个UDP套接字从若干个不同的客户接收数据报,每个客户一个数据报

TCP通过给其中每个分节关联一个序列号对所发送的数据进行排序,提供动态估算往返时间RTT,超时和重传等机制,同事提供流量控制(flow control),TCP连接是全双工的。

MSS选项。发送SYN的TCP一端使用本选项通告对端它的最大分节大小(maximum segment size),也就是它在本连接的每个TCP分节中愿意接受的最大数据量。发送端TCP使用接收端的MSS值作为所发送分节的最大大小。
最小重组缓冲区大小(536 MSS) - 单个TCP分节大小
MSS经常设置成MTU减去IP和TCP首部的固定长度。在以太网中使用IPv4的MSS值为1460,使用IPv6的MSS值为1440(两者的TCP首部都是20字节,但IPv4首部是20字节,IPv6首部是40字节)

ping和traceroute是使用ICMP协议实现的网络诊断应用。traceroute自行构造UDP分组来发送并读取所引发的ICMP应答

TIME_WAIT

TIME_WAIT状态
该端点停留在这个状态的持续时间是最长分节生命期(maximum segment lifetime, MSL)的两倍,称之为2MSL

端口号

TCP、UDP协议都使用16位整数的端口号(port number)来区分这些进程 0-1023~1024-65535
服务器通常使用熟知的端口号来标识某个服务,而客户端通常使用短期存活的临时端口,这些端口号通常由传输层协议自动赋予客户。

一个TCP连接的套接字对(socket pair)是一个定义该连接的两个端点的四元组:本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号。
bind函数要求应用程序给TCP、UDP套接字指定本地IP地址和本地端口号

数据报大小的一些限制

IPv4数据报的最大大小是65535字节,包括IPv4首部。
IPv4的DF(don't fragment)位和隐含DF位可用于路径MTU发现。

缓冲区写数据

内核直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区,才从write系统调用返回。从写一个TCP套接字的write调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对端的TCP或应用进程已接收到数据
write_buff

基本套接字编程

当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(也就是以指向该结构的指针)来传递

// 通用套接字地址结构sockaddr
int bind(int, struct sockaddr*, socklen_t);

bind(sockfd, (strcut sockaddr*) &serv, sizeof(serv));

不同套接字地址结构
sockaddr_struct

套接字地址结构是在进程和内核之间传递的
(1)从进程到内核传递套接字地址结构的函数有3个:bind、connect和sendto,这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小
connect(sockfd, (SA*) &serv, sizeof(serv));
(2)从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname和getpeername,这4个函数的其中两个参数是指向某个套接字地址结构的指针和指向表示该结构大小的证书变量的指针
len = sizeof(cli); /* len is a value /
getpeername(sockfd, (SA
) &serv, &len);
把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构大小是一个值(value),它告诉内核该结构的大小,这样内核在写该结构时不至于越界;当函数返回时,结构大小又是一个结果(result),它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数被成为值-结果(value-result)参数

字节序

术语小端大端表示多个字节值的哪一端(最低有效位,最高有效位),存储在该值的起始位置。
多字节发送需要考虑网络和本地字节序。

// 传递二进制结构和文本字符串
struct args {
  long arg1;
  long arg2;
};
// 二进制结构 需要考虑字节序
write(sockfd, &args, sizeof(args));

char line[MAXLINE];
// 文本字符串
write(sockfd, line, strlen(line));

解决方法通常是:
1.把所有的数值数据作为文本串来传递。
2.显示定义所支持数据类型的二进制格式(位数、大端或小端字节序),并以这样的格式在客户与服务器之间传递所有数据。

union {
  short s;
  char c[sizeof(short)];
} un;
un.s = 0x0102;

在每个TCP分节中都有16位的端口号和32位的IPv4地址。发送协议栈和接收协议栈必须就这些多字节字段各个字节的传送顺序达成一致。网际协议中使用大端字节序来传送这些多字节整数。

// Berkeley
#include <strings.h>
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);

// ANSI c
#include <string.h>
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);

addr转换

inet_pton和inet_ntop中p和n分别代表表达(presentation)和数值(numeric)

客户端、服务器模型

client_server_model

tcp_timeline

tcp_state_trans

#include <sys/socket.h>
// 协议族,套接字类型,协议类型
// SOCK_STREAM-字节流套接字  SOCK_DGRAM-数据报套接字
int socket(int family, int type, int protocol);

// 激发TCP的三次握手过程,而且仅在连接建立成功或出错时才返回
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

// bind函数把一个本地协议地址赋予一个套接字 
// 通配地址 INADDR_ANY
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

// SYN_RCVD和ESTABLISHED两队列之和不超backlog OR 模糊因子,乘以1.5得到未处理队列最大长度
// backlog参数的确切含义,它应该指定某个给定套接字上内核为之排队的最大已完成连接数
int listen(int sockfd, int backlog);

// 从已完成连接队列队头返回一个已完成连接,如果该队列为空,那么进程被投入睡眠
// 假定套接字为默认的阻塞方式
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

#include <unistd.h>
int close(int sockfd);
/*
 close一个TCP套接字的默认行为是把该套接字标记成已关闭,然后立即返回到调用进程。
 该套接字描述符不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
 然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。
 */
#include <unistd.h>
// 调用一次,返回两次
// 父进程中调用fork之前打开的所有描述符在fork返回之后由子进程共享
/*
 我们将看到网络服务器利用了这个特性:父进程调用accept之后调用fork。所接受的已连接套接字随后就在父进程与子进程之间共享。
 通常情况下,子进程接着读写这个已连接套接字,父进程则关闭这个已连接套接字。
 */ 
pid_t fork(void);
/*
 fork有两个典型的用法:一个进程创建一个自身的副本 or 一个进程想要执行另一个程序,exec把当前进程映像替换成新的程序文件,而且该新程序通常从main函数开始执行。进程ID并不改变。
 进程在调用exec之前打开着的描述符通常跨exec继续保持打开
 */
/*
 这6个exec函数之间的区别在于:
 1. 待执行的程序文件是由文件名(filename)还是由路径名(pathname)指定
 2. 新程序的参数是一一列出还是由一个指针数组来引用
 3. 把调用进程的环境传递给新程序还是给新程序指定新的环境
 */
// 以空指针结束可变数量的这些参数
int execl(const char *pathname, const char *arg0, ... /* (char*)0 */);
// 常量指针 declare argv as array of const pointer to char
// 没有指定参数字符串的数目,argc数组必须含有一个用于指定其末尾的空指针
int execv(const char *pathname, char *const argv[]); 
  // 原书这里有笔误:char *const *argv[] 
int execle(const char *pathname, const char *arg0, ... /* (char*)0, char *const envp[] */); 
int execve(const char *pathname, char *const argv[], char *const envp[]); 
int execlp(const char *filename, const char *arg0, ... /* (char*)0 */);
int execvp(const char *filename, char *const argv[]);
/*
 这些函数只在出错时才返回到调用者。否则,控制将被传递给新程序的起始点,通常就是main函数
 */

exec函数之间的关系:
exec_fam_function

close

描述符引用计数:并发服务器中父进程关闭已连接套接字只是导致相应描述符的引用计数减1

SIGCHLD

在服务器子进程终止时,给父进程发送一个SIGCHLD信号,如果父进程未加处理,该信号的默认行为是被忽略,子进程于是进入僵死状态--必须处理僵死进程
无论何时fork子进程都得wait它们,以防变成僵死进程

// 信号处理函数由信号值这个单一的整数参数来调用,且没有返回值,原型如下:
void handler(int signo);
// 第一个参数是信号名,第二个参数指向函数的指针
void (*signal(int signo, void (*handler)(int))) (int);

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);

简单wait,还是会有僵尸进程,而且每次僵尸进程的数量都不定。Linux的信号机制是不排队的,假如在某一时间段多个子进程退出后都会发出SIGCHLD信号,但父进程来不及一个一个地响应,所以最后父进程实际上只执行了一次信号处理函数。但执行一次信号处理函数只等待一个子进程退出,所以最后会有一些子进程依然是僵尸进程。
如果调用wait的进程没有已终止的进程,不过有一个或多个子进程仍在执行,那么wait将阻塞到现有子进程第一个终止为止。
waitpid函数的pid参数允许我们指定想等待的进程ID,值-1表示等待第一个终止的子进程。options参数允许我们指定附加选项。最常用的选项就是WNOHANG,它告知内核在没有已终止子进程时不要阻塞。

I/O多路复用

阻塞在一个IO未接收到其他通知:当TCP客户同时处理两个输入:标准输入和TCP套接字。我们遇到的问题就是在客户阻塞于(标准输入上的)fgets调用期间,服务器进程会被杀死。服务器TCP虽然正确的给客户TCP发送了一个FIN,但是既然客户进程正阻塞于从标准输入读入的过程,它将看不到这个EOF,直到从套接字读时为止(可能已经过了很长时间)。这样的进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(也就是说输入已准备号被读取,或者描述符已能承接更多的输出),它就通知进程。这个能力称为I/O复用,是由select和poll这两个函数支持的。
有些系统提供了让进程在一串事件上等待的机制。轮询设备(poll device)就是这样的机制之一。

I/O模型:阻塞式I/O、非阻塞式I/O、I/O复用(select和poll)、信号驱动式I/O(SIGIO)、异步I/O(POSIX的aio_系列函数)

一个输入操作通常包含两个不同的阶段:

  1. 等待数据准备好,对一个套接字上的操作,第一步通常等待数据从网络中到达。当所等待的分组到达时,它被复制到内核中的某个缓冲区。
  2. 从内核向进程复制数据,把数据从内核缓冲区复制到应用进程缓冲区。
#include <sys/select.h>
#include <sys/time.h>
// 允许进程指示内核等待多个事件中的任何一个发生
// 忘了对最大描述符+1,忘了描述符集是值-结果参数
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

struct timeval {
  long tv_sec; // seconds
  long tv_usec; // microseconds 微秒   millisecond-毫秒
};


#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
struct pollfd {
  int fd;
  short events;
  short revents;
};

当一个服务器在处理多个客户时,它绝对不能阻塞于只与单个客户相关的某个函数调用,否则可能导致服务器被挂起,拒绝为所有其他客户提供服务。这就是所谓的拒绝服务型攻击。
可能的解决办法包括:1. 使用非阻塞式I/O; 2. 让每个客户由单独的控制线程提供服务; 3. 对I/O操作设置一个超时。

基本UDP套接字编程

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to socklen_t addrlen);

非阻塞式I/O

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

待深究问题

socket backlog : tcp 队列{syn队列、accept队列}
对于select、epoll + 非阻塞I/O理解还不够
socket缓冲区
需要重新细看一下第16章:非阻塞式I/O

单例模式

设计模式经典GoF定义的单例模式需要满足以下两个条件:

  • 保证一个类只创建一个实例。
  • 提供对该实例的全局访问点。

如果系统有类似的实体(有且只有一个,且需要全局访问),那么就可以将其实现为一个单例。实际工作中常见的应用举例

  • 日志类,一个应用往往只对应一个日志实例。
  • 配置类,应用的配置集中管理,并提供全局访问。
  • 管理器,比如windows系统的任务管理器就是一个例子,总是只有一个管理器的实例。
  • 共享资源类,加载资源需要较长时间,使用单例可以避免重复加载资源,并被多个地方共享访问。
template<typename T>  
class Singleton : boost::noncopyable  
{
    public:
        static T& instance()
        {
            pthread_once(&ponce_, &Singleton::init);
            return *value_;
        }

        static void init()
        {
            value_ = new T();
        }
    private:
        static pthread_once_t ponce_;
        static T* value_;

/*
private:
    Singleton();                            // ctor hidden
    Singleton(Singleton const&);            // copy ctor hidden
    Singleton& operator=(Singleton const&); // assign op. hidden
    ~Singleton();                           // dtor hidden
 */
};

template<typename T>  
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;

template<typename T>  
T* Singleton<T>::value_ = NULL;  

Blog Site

pbdq blog

https://pbdq.github.io

Data From:https://github.com/v4if/blog/issues

思路

使用issue写文章,将图片等资源放在{repo}/assets下做存储,利用Github API获取issue内容,Github Pages构造单页面应用

静态博客,无后台搭建,自动拉取issues内容渲染

Hack

// api
https://api.github.com/repos/v4if/blog/issues

默认会返回30条issue,为了拉取全部的issue,采用了一种相对hack的方法

page: [int], // 当前页
per_page: [int] // 获取的条数

这两个参数可以用get的方式带上

https://api.github.com/repos/v4if/blog/issues?page=1&per_page=1000

C++ 编译时常量性

哪些地方需要编译时常量

常量表示该值不可修改,通常是通过const关键字修饰,比如const int i = 3;,但const描述的是运行时常量性的概念,即具有运行时数据的不可更改性。

但有的时候需要的却是编译时期的常量性,可以在编译时进行值计算,在没有constexpr之前可以使用define定义编译时常量

#define N 1

int arr[N] = {0}; //初始化数组

enum {e1 = N, e2}; //匿名枚举

switch (cond) { //switch-case
  case N: break;
  default: break;
}

template<int i = N> //模板函数的默认模板参数
void func_args_template(){}

C++11 constexpr

constexpr,即常量表达式

constexpr int get_compile_const() {return 1;}

有了constexpr,编译器就可以在编译时期对get_compile_const表达式进行值计算(evaluation),从而将其视为一个编译时期的常量

常量表达式函数

通常可以在函数返回类型前加入关键字constexpr来使其成为常量表达式函数,但常量表达式函数要求非常严格:

  • 函数体内只有一条语句,且该条语句必须是返回语句,static_assert``using``typedef通常不会造成问题
  • 常量表达式必须返回值
  • 常量表达式函数在使用前必须被定义,而不只是函数声明,函数调用与值计算
  • 返回的常量表达式中,不能使用非常量表达式的函数

常量表达式值

const int i = 1;
constexpr int j = 1;

两者在大多数情况下是没有区别的。不过有一点,就是如果i在全局名字空间中,编译器一定会为i产生数据。而对于j,如果不是有代码显示地使用了它的地址,编译器可以选择不为它产生数据,而仅将其当做编译时期的值,好像光有名字没有产生数据的枚举值,以及不会产生数据的右值字面量。事实上,也都只是编译时期的常量

常量表达式构造函数

struct MyType{
  constexpr MyType(int x):i(x){}
  int i;
};
constexpr MyType mt = {0};

常量表达式构造函数必须满足一下两点:

  • 函数体必须为空
  • 初始化列表只能由常量表达式赋值

常量表达式用于模板函数

template<typename T>
constexpr T const_exp(T t) {return t;}

不过由于模板的不确定性,模板函数是否被实例化为一个能够满足编译时常量性的版本通常也是未知的。C++11标准规定,当声明为常量表达式的模板函数后,而某个该模板函数的实例化结果不满足常量表达式需求的话,constexpr会被自动忽略,该实例化后的函数将成为一个普通函数

参考资料

《深入理解C++11-新特性解析与应用》

Shell 脚本_黑魔法

$()、${}、$(())

$(command) is “command substitution”. 命令替换 相当于``

$ echo $(date)

> 2017-05-05-15:44:38

${parameter} is “parameter substitution”. 变量提取

$ animal = 'cat'
$ echo ${animal}_food

> cat_food

$(())是用来作整数运算的

$ echo $((5 ** 2))

> 25

记录CMakelist的一些配置

C++11 thread线程库

cmake_minimum_required(VERSION 3.8)
project(thread_test)

set(CMAKE_CXX_STANDARD 11)

set(SOURCE_FILES main.cpp)

add_executable(thread_test ${SOURCE_FILES})
target_link_libraries (thread_test pthread)

关闭返回值优化

set(CMAKE_CXX_FLAGS -fno-elide-constructors)

C++ 原子操作

原子操作

所谓原子操作,就是多线程程序中最小的且不可并行化的操作。通常对一个共享资源的操作是原子操作的话,意味着多个线程访问资源时,有且仅有唯一一个线程在对这个资源进行操作。那么线程(处理器)的角度看来,其他线程就不能够在本线程对资源访问期间对该资源进行操作,因此原子操作对于多个线程而言,就不会发生有别于单线程程序的意外情况。

原子性,也就是要么全部做完,要么全部不做

在多进程(线程)访问资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomic operation)是不需要同步,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

硬件机制所保证的原子性

  • (1)一些基本的内存读写操作是本身已经被硬件提供了原子性保证(例如读写单个字节的操作);
  • (2)一些需要保证原子性但是没有被第(1)条机制提供支持的操作(例如read-modify-write)可以通过使用”LOCK#”来锁定总线,从而保证操作的原子性
  • (3)因为很多内存数据是已经存放在L1/L2 cache中了,对这些数据的原子操作只需要与本地的cache打交道,而不需要与总线打交道,所以CPU就提供了cache coherency机制来保证其它的那些也cache了这些数据的processor能读到最新的值

atomic

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<long long> total{0}; //只能从模板参数进行构造

void thread_func() {
    for (long long i = 0; i < 100000000LL; ++i) {
        total += 1;
    }
}

int main() {
    std::thread t1(thread_func);
    std::thread t2(thread_func);

    t1.join();
    t2.join();

    std::cout << total << std::endl;
    return 0;
}

使得原子类型能够在线程间保持原子性的缘由主要还是因为编译器能够保证针对原子类型的操作都是原子操作。原子操作都是平台相关的,因此有必要为常见的原子操作进行抽象,定义统一的接口,并根据编译器选项(或环境)产生其平台相关的实现

2017-08-13_15-23-10

在C++11中,标准将原子操作定义为atomic模板类的成员函数,这囊括了绝大多数典型的操作,如读、写、交换等。当然,对于内置类型而言,主要是通过重载一些全局操作符来完成的。上述代码中的+=,使用g++进行编译的话,会产生一条特殊的lock前缀的x86指令,lock能够控制总线及实现x86平台上的原子性加法

atomic_flag是一个比较特殊的布尔型的atomic类型(atomic_flag和atomic_bool是不同的):相比于其他的atomic类型,atomic_flag是无锁的,即线程对其访问不需要加锁,因此对atomic_flag而言,也就不需要使用load、store等成员函数进行读写(或者重载操作符),可以基于此实现自旋锁

/* 无锁编程 */
void lock(atomic_flag* lock){
    while(lock.test_and_set());//用于在一个内存空间原子地写入新值并且返回旧值
}
void un_lock(atomic_flag* lock) {lock.clear();}

内存模型:顺序一致性与memory_order

如果只是简单的想在线程间进行数据的同步的话,原子类型已经提供了一些同步的保障。不过这样做的安全性确实建筑与一个假设之上,即所谓的顺序一致性(sequential consistent)的内存模型(memory model)

#include <thread>
#include <atomic>
#include <iostream>
using namespace std;

atomic<int> a;
atomic<int> b;

int thread1(int) {
    int t = 1;
    a = t;
    b = 2;
}

int thread2(int) {
    while(b != 2) ; //自旋等待
    cout << a << endl; //总是期待a的值为1
}

int main() {
    thread t1(thread1, 0);
    thread t2(thread2, 0);
    
    t1.join();
    t2.join();
    return 0;
}

如果编译器认定a、b的赋值语句的执行先后顺序对输出结果没有任何影响的话,则可以依情况将指令重排序(reorder)以提高性能。而如果a、b赋值语句的执行顺序必须是a先b后,则编译器不会执行这样的优化

实际上默认情况下,在C++11中的原子类型的变量在线程中总是保持这顺序执行的特性(非原子类型则没有必要,因为不需要在线程间进行同步)。我们称这样的特性为顺序一致的,即代码在线程中运行的顺序与程序员看到的代码顺序一致,a的赋值语句永远发生在b的赋值语句之前

通常情况下,内存模型通常是一个硬件上的概念,表示的是机器指令是以什么样的顺序被处理器执行的。现代的处理器并不是逐条处理机器指令的,可能乱序发射,即在一个时钟周期里发射多条指令。那么强顺序意味着:对于多个线程而言,其看到的指令顺序是一致的。具体地,对于共享内存的处理器而言,需要看到内存中的数据被改变的顺序与机器指令中的一致。在现实中,x86都是被看作采用强顺序内存模型的平台。对于任何一个线程而言,其看到原子操作都是顺序的。而对于采用弱顺序内存模型的平台,比如PowerPC、Armv7这样的平台而言,如果要保证指令执行的顺序,通常需要由在汇编指令中加入一条所谓的内存栅栏(memory barrier)指令,迫使已进入流水线中的指令都完成后处理器才执行sync以后指令(排空流水线),这样sync之前运行的指令总是先与sunc之后的指令完成的

在高级语言和机器指令间还有一层隔离,这层隔离就是由编译器来完成的,因此是否指令重排序优化,加入内存栅栏都是编译器做的。因此对于弱内存模型的平台,原子操作不仅禁止了编译器的优化,还插入了大量的内存栅栏,为了解除这样的性能约束,C++11设计者给出的解决方式是让程序员为原子操作指定所谓的内存顺序:memory_order,在顺序无关的情况下可以采用一种松散的内存模型来放松对原子操作的执行顺序的要求,如a.store(1, memory_order_relaxed);,该指令可以任由编译器重排序或者由处理器乱序执行

2017-08-13_16-22-18

C++11中所有atomic原子操作的默认值为memory_order_seq_cst必须是顺序一致的。

参考资料

C++ 中的原子性操作
《深入理解C++11-新特性解析与应用》

C++ 返回值优化

当一个未命名且未绑定到任何引用的临时变量被移动或复制到一个相同的对象时,拷贝和移动构造可以被省略。当这个临时对象在被构造的时候,他会直接被构造在将要拷贝/移动到的对象。当未命名临时对象是函数返回值时,发生的省略拷贝的行为被称为RVO,"返回值优化"。

其目的是为了优化掉栈上的临时对象。

#include <iostream>

struct BigObject{
    BigObject() { std::cout << "construct.\n"; }
    BigObject(const BigObject&) { std::cout << "copy construct.\n"; }
    BigObject(BigObject&&) { std::cout << "move construct.\n"; }
    ~BigObject() { std::cout << "destruct.\n"; }
};

/*
NRVO Named Return Value Optimization
命名返回值优化
 */
BigObject foo() {
    BigObject localObj;
    std::cout << std::hex << &localObj << std::endl;
    return localObj;  
}

/*
RVO Return Value Optimization
返回值优化
 */
BigObject bar() {
    return BigObject(); 
}


int main() {
    BigObject o = foo();
    std::cout << std::hex << &o << std::endl;
    return 0;
}
/*
construct.
0x7ffd0e56deef
0x7ffd0e56deef
destruct.
 */

可以看到foo中的temp和main中v0所在的地址值是相同的,且只有一次构造和析构操作,因此可以看到返回的类对象直接被构造在将要拷贝/移动到的对象栈空间上。

2e8834591ba5295d242d6da33bca839a_b

图片来源:知乎-什么是完整的RVO以及NRVO过程

在看了维基百科上关于返回值优化的解释之后,对于函数返回类对象从实现角度,一种实现办法是在函数调用语句前在stack frame上声明一个隐藏对象,把该对象的地址隐蔽传入被调用函数,函数的返回对象直接构造或者复制构造到该地址上。

struct BigObject {};

BigObject foo() {
  BigObject ret;
  // generate ret
  return ret;
}

int main() {
  BigObject o = foo();
}

可能产生的代码如下:

struct BigObject {};

BigObject * foo(BigObject * _hiddenAddress) {
  BigObject ret = {};
  // copy result into hidden object
  *_hiddenAddress = ret;
  return _hiddenAddress;
}

int main() {
  BigObject _hidden; // create hidden object
  BigObject o = *foo(&_hidden); // copy the result into d
}

这引起了BigObject对象被复制两次,也就是上图中左侧图片描述的过程。

优化之后可能会产生如下的代码:

struct BigObject {};

void f(BigObject& ret_value) {
  BigObject localObj;
  return ret_value.BigObject::BigObject(std::move(localObj));//显式构造
}

int main() {
  BigObject o; ///这里没有使用默认构造,定义而不构造
  f(&o);
}

返回的类对象直接被构造在将要拷贝/移动到的对象栈空间上,只会产生一次构造/析构,优化掉了栈上的临时对象。

大部分C++编译器均支持返回值优化。在某些环境下,编译器不能执行此优化。一个常见情形是当函数依据执行路径返回不同的命名对象,或者命名对象在asm内联块中被使用

#include <iostream>

struct C {
	C(int j) { i = j; }
	C(const C&) { std::cout << "A copy was made.\n"; }

	int i;
};

C  f(bool cond = false) {
	C first(101);
	C second(102);
	// the function may return one of two named objects
	// depending on its argument. RVO might not be applied
	return cond ? first : second;
}

int main() {
	std::cout << "Hello World!\n";
	C obj = f(true);
}

参考

维基百科-返回值优化

聊聊内存管理

最近了解内存管理,也看了不少关于内存管理的机制和每家对于内存管理这个老大难问题的一些做法,比如Linux利用buddy伙伴算法管理物理页,内核直接映射的896M空间采用slab机制,主要总结一下关于自己看到的一些东西和看法。

为什么需要内存管理

这也是我看内存管理的时候一直存在的一个问题,现在慢慢的有了一点理解。

memory

开始脑补假如没有内存管理,给定一段内存区域,然后用指针直接进行访存,假设完全一点,没有虚拟内存那一套,先考虑单进程A,假设通过ptr_1,ptr_2,ptr_3对内存进行访存,首先会出现的问题可能就是边界不明,ptr_1可能越界访问ptr_2的内容,然后就是使用的时候不知道哪段内存区域可以使用,哪一段内存区域上有其他数据不能使用。在多进程下,多个进程之间没有隔离。

先讨论第二个问题,现在都知道是通过虚拟映射,中间加一层的方法解决的,每个进程看到的地址空间都是0-4G,互相隔离。

对于第一个问题,自然就想到了将内存分块进行管理,比较简单的思路是将内存分成固定大小的块,然后用一段空间来存储内存使用情况的bitmap,该块被使用标记为1。块的大小取经验值:4K,然后分配的时候只能分配标记为0的内存块,即空闲内存块,好像是解决了第一个问题。

但是又会带来新的问题:外内存碎片。多次对内存进行分配和回收之后,内存中会出现很多空洞,将空闲内存隔成一个个小的内存区间,因此在分配大内存的时候会由于内存块大小太小而无法分配足够的空闲块,所以引入了buddy伙伴算法,按照2的幂次方进行管理。

假设块的大小取4K,在分配一些小内存的时候又会出现内碎片,即已被分配出去但是用不到的内存空间,所以引入了slab机制和STL的二级分配器,其实Linux的虚拟内存管理也会对内碎片起到一定的缓冲作用。

内碎片是已被分配出去但是用不到的内存空间,外碎片是由于大小太小而无法分配出去的空闲块。

因此为了解决这两个头疼的问题,出现了一系列的内存管理机制。

Linux内存管理

linux_mm_arch

从下往上看,首先是buddy伙伴算法的物理页内存管理,MAX_ORDER通常为11,2^10 1024个页面
buddy

虚拟内存与真实物理内存整体映射关系
map_entry

先看用户空间,虚拟内存的申请有两种方式brk和mmap

brk通过_edata指针从堆区开始往上推内存,分配的虚拟内存会有一段区域存储header信息,malloc函数分配内存,如果请求内存大于128K,那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。brk分配的内存需要等到高地址内存释放以后才能释放,而mmap分配的内存可以单独释放。

brk_mmap_1

brk_mmap_2

brk_mmap_3

再看内核空间,高端内存映射和直接映射。高端内存映射通过vm_struct管理,因此走内核页表,MMU那一套得到物理地址,因此对内碎片的问题有所缓解;物理内存映射区采取直接映射的做法,即虚拟地址经过一定的线性偏移即可得到物理地址,那么这部分的内碎片问题怎么办,加一个中间层slab机制,基于缓冲和池子的**对上层提供一个分配小内存块chunk和object对象的机制。L1_CACHE_BYTES值为32,KMALLOC_MAX_SIZE值为4194304,内核slab分配器能够默认的提供32-4194304共20种内存长度分档

kmem_cache是一个cache_chain的链表,描述了一个高速缓存,每个高速缓存包含了一个slabs的列表,这通常是一段连续的内存块。存在3种slab:slabs_full(完全分配的slab),slabs_partial(部分分配的slab),slabs_empty(空slab,或者没有对象被分配)。slab是slab分配器的最小单位,在实现上一个slab有一个或多个连续的物理页组成(通常只有一页)。单个slab可以在slab链表之间移动,例如如果一个半满slab被分配了对象后变满了,就要从slabs_partial中被删除,同时插入到slabs_full中去。
slab

Linux地址转换

捎带着看一下Linux地址是如何转换的,程序在执行时,传递给CPU的地址是逻辑地址,它由两部分组成,一部分是段选择符(比如cs和ds等段寄存器的值), 另一部分为有效地址(即偏移量,比如eip寄存器的值)。逻辑地址必须经过映射转换变为线性地址, 线性地址再经过一次映射转为物理地址,才能访问真正的物理内存。
addr_flex

STL内存管理

STL的内存管理分为两级分配器,当大于128Byte时调用第一级分配器,否则调用第二级分配器,相当于实现了一个free-list,16个空闲链表分别管理大小为8、16、24......120、128的数据块
free-list

Effective C++ 笔记

导读

被声明为explicit的构造函数,禁止编译器执行非预期的类型转换,除非有一个好的理由允许构造函数被用于隐式类型转换,否则一般声明为explicit

copy构造函数用来以同类型对象初始化自我对象,copy assignment操作符用来从另一个对象中拷贝其值到自我对象

Pass-by-value意味着调用copy构造函数,Pass-by-reference-to-const往往是比较好的选择

命名习惯

lhs和rhs,分别代表left-hand side左手端和right-hand side右手端

通常将指向一个T型对象的指针命名为pt,意思是pointer to T

让自己习惯C++

尽量以const、enum、inline替换#define

const double PI = 3.14;

在class内旧式编译器不支持static const int Magic = 0x5;static成员在其声明式上获得初值
enum hack补偿做法:一个属于枚举类型的数值可权充int被使用enum {Magic = 0x5};

temlate inline 函数可以代替宏

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

// 可预料行为和类型安全检查
template<typename T>
inline void CallWithMax(const T& a, const T& b) {
  f(a > b ? a : b);
}

有了const、enum和inline,对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef仍然是控制编译的重要角色

  1. 对于单纯常量,最好以const对象或enum替换#define
  2. 对于形似函数的宏(macros),最好改用inline函数替换#define

尽可能使用const

不该被改动

const修饰常量

const出现在左边,表示被指物是常量;如果出现在右边,表示指针自身是常量

const面对函数声明时的应用

令函数返回一个常量值const T opertor*(const T& lhs, const T& rhs);

const成员函数
两个成员函数如果只是常量性不同,可以被重载

void print(const TextBlock& ctb) {
  std::cout << ctb[0]; // 调用const char& operator[](std::size_t position) const
}

mutable可以释放掉non-static成员变量的const成员函数约束

在const和non-const成员函数中避免代码重复

// 令non-const operator[]调用其const —— 需要转型操作
// 反向做法,令const版本调用non-const版本是不应该做的事
// 因为const成员函数承诺绝不改变其内部对象,non-const却没有
char& operator[](std::size_t position) {
  return const_cast<char&>(
    static_cast<const T&>(*this)[position]
  );
}

确定对象被使用前已先被初始化

读取未初始化的值会导致不明确的行为,更可能的情况是读入一些半随机bits

C++规定,对象的成员变量的初始化动作发生在进入构造函数之前,因此在构造函数内的操作都是赋值,而非初始化,初始化的发生时间更早
构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作

对于成员初始化列表,C++有着十分固定的成员初始化次序,base classes更早于derived classes被初始化,而class的成员变量总是以其声明次序被初始化

构造/析构/赋值运算

了解C++默默编写的函数

copy构造函数、copy assignment操作符、析构函数、default构造函数,唯有当这些函数被需要(被调用),它们才会被编译器创建出来
所有这些函数都是public且inline的

如果在一个内含reference成员的class内支持赋值操作(assignment),必须自己定义copy assignment操作符,C++并不允许让reference改指向不同的对象

若不想使用编译器自动生成的函数,就该明确拒绝

可以将copy构造函数或copy assignment操作符声明为privete
但这个做法并不绝对安全,因为member函数和friend函数还是可以调用private函数
因此可以只声明,而且故意不去实现它们,调用时会得到一个连接错误(linkage error)

T(const T&);
T& operator=(const T&);

为多态基类声明virtual析构函数

给base classes一个virtual析构函数,这个规则只适用于带多态性质的base classes身上,这种base classes的设计目的就是为了使用指向base的指针,用来通过base class接口处理derived class对象
某些classes的设计目的是作为base classes使用,但不是为了多态用途,因此它们不需要virtual析构函数

基类不添加虚析构函数会造成内存泄漏,局部销毁
给析构函数加上virtual之后,就等于告诉编译器应该调用的是哪个析构函数的信息了,这样一来,当你的派生对象经由基类指针删除时,就会非常顺利的调用到派生类的析构函数,而不是调用到基类析构函数。
虚函数的副作用是有的,类中只要有大于等于一个虚函数时就会生成虚表 vptr,这个虚表会占用内存空间,所以,我认为当你完全了解了这个机制后,可以自己度量这个性能得失,并且做好注释工作。不过,一点点的性能损失来换取绝对的安全,这是值得的。

许多人的心得是:只有当class内含至少一个virtual函数,才为它声明virtual析构函数

不要试图继承一个不带non-virtual析构函数的class

以下代码会造成内存泄露,只是局部销毁了Base的成分

// g++ -O -g -fsanitize=address virtual_base.cpp
#include <vector>
class Base
{
public:
  ~Base(){};
};

class Derived : public Base
{
private:
  std::vector<int> m_data;
};

int main()
{
  Base *obj = new Derived();
  delete obj;

  return 0;
}

抽象class

class AWOV {
  public:
    virtual ~AWOV() = 0;
};

析构函数的运作方式是,最深层派生的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用

别让异常逃离析构函数

抢先制不明确行为于死地

DBConn::~DBConn() {
  try {db.close();}
  catch (...) {
    // 记录下对close的调用失败
    std::abort();
  }
}

或者吞掉异常不处理

绝不在构造和析构过程中调用virtual函数

令operator=返回一个reference to *this

在operator=中处理自我赋值

潜在的自我赋值a[i] = a[j] *px = *py

// 不能处理new异常
Widget& Widget::operator=(const Widget& rhs) {
  if (this == &rhs) return *this;

  delete pb;
  pb = new Bitmap(*rhs.pb);
  return *this;
}

// 如果new Bitmap抛出异常,pb会保持原状
// 对原bitmap做了一份复件,删除原bitmap,指向新制造的bitmap
Widget& Widget::operator=(const Widget& rhs) {
  Bitmap* pOrig = pb;
  pb = new Bitmap(*rhs.pb);
  delete pOrig;
  return *this;
}

复制对象时勿忘其每一个成分

如果为class添加一个成员变量,必须同时修改copying函数
为派生类撰写copying函数,必须很小心的也复制其base class成分
Base::opreator=(rhs);

资源管理

常见的资源包括动态内存分配、文件描述器、互斥锁、数据库连接、网络socket

以对象管理资源

把资源放进对象内,依赖C++的析构函数自动调用机制确保资源被释放

  1. 获得资源后立刻放进管理对象内
// not good
// 工厂函数,返回动态分配对象
Investment* pInv = createInvestment();
...
delete pInv;

// 智能指针
std::auto_ptr<Investment> pInv(createInvestment());

注意不要让多个auto_ptr同时指向同一对象
为了预防这个问题,auto_ptr有一个不寻常的性质:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权!
auto_ptr的底层条件是:受auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它

shared_ptr会持续追踪共有多少个对象指向某笔资源,并在无人指向它时自动删除该资源。但无法打破环状引用,比如两个其实已经没有被使用的对象彼此互指,因而好像还处在被使用的状态

auto_ptr和shared_ptr两者都在其析构函数内做delete,而不是delete[]动作,意味着不要在动态分配的array身上使用它们
vector和string几乎总是可以取代动态分配而得的数组
2. 管理对象运用析构函数确保资源被释放,当对象离开作用域

在资源管理类中小心copy行为

资源取得时机便是初始化时机

RAII —— 资源获取即初始化,利用对象的声明周期进行底层的资源管理

可以采取的策略:

  1. 禁止复制
  2. 对底层资源祭出引用计数法
class Lock{
  public:
  // 以某个mutex初始化shared_ptr,并以unlock函数为删除器
    explicit Lock(Mutex* pm) : mutexPtr(pm, unlock) {
      lock(mutexPtr.get());
    }
  private:
    shared_ptr<Mutex> mutexPtr;
};
  1. 复制底部资源,注意深度拷贝
  2. 转移底部资源的拥有权

在资源管理类中提供对原始资源的访问

shared_ptr和auto_ptr都提供一个get成员函数,用来执行显示转换,也就是返回智能指针内部的原始指针(的复件)
就像(几乎)所有的智能指针一样,shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator* )它们允许 隐式转换至底部原始指针

成对使用new和delete时要采取相同的形式

delete[] ptr;

当使用new时,有两件事情发生,一是内存被分配出来,二是针对此内存会有构造函数被调用
使用delete时,也有两件事情发生,针对此内存会有析构函数被调用,然后内存才被释放

delete最大的问题在于:即将被删除的内存之内究竟存有多少对象
单一对象的内存布局一般而言不同于数组的内存布局。更明确的说,数组的内存布局通常还包括数组大小的记录,以便delete知道需要调用多少次析构函数

以独立语句将new的对象置入智能指针

// 在单独语句内以智能指针存储new的对象
// 如果不这样,一旦异常抛出,有可能导致难以察觉的资源泄露
shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());

设计与声明

让接口容易被正确使用,不易被误用

对string使用length,对list使用size(告诉调用者目前容器内有多少对象)

Boost的shared_ptr是原始指针的两倍大,以动态分配内存作为簿记用途和删除之专属数据,以virtual形式调用删除器,并在多线程程序修改引用次数时蒙受线程同步化的额外开销

设计class犹如设计type

  1. 新type的对象应该如何被创建和销毁:构造和析构
  2. 对象的初始化和对象的赋值该有什么样的差别:构造函数和赋值操作符
  3. 新type的对象如果被passed by value(以值传递),意味着什么:copy构造函数
  4. 什么是新type的合法值:构造、赋值操作符、setter函数必须进行有效的错误检查
  5. 新的type需要配合某个继承图系吗:尤其是析构函数,是否为virtual
  6. 新的type需要什么样的转换:隐式和显示转换
  7. 什么样的操作符和函数对此新type是合理的:class声明哪些函数,某些该是member函数,某些则否
  8. 什么样的标准函数应该驳回:那些正是应该被声明为private的
  9. 谁该取用新type的成员:考虑friends

宁以pass-by-reference-to-const替换pass-by-value

以pass-by-value的形式传递需要带来临时副本的拷贝构造和析构的额外开销
避免derived class对象切割问题

但是对于内置类型来说(如int),pass by value往往比pass by reference(通常意味着传递的是指针)的效率更高些
可以合理假设pass-by-value并不昂贵的唯一对象就是内置类型和STL的迭代器和函数对象

必须返回对象时,别妄想返回其reference

const Rational& operator*(const Rational& lhs, const Rational& rhs) {
  // 更糟糕的写法,谁来为new出来的对象实施delete
  Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}
// 欲令诸如operator*这样的函数返回reference,只是浪费时间而已

将成员变量声明为private

从封装的角度看,其实只有两种访问权限:private(提供封装)和其他(不提供封装)

宁以non-member、non-friend替换member函数

将所有便利函数放在多个头文件内但隶属于同一个命名空间,意味着对于客户来说可以轻松扩展这一组函数
因为class定义式对客户而言是不能扩展的

若所有参数皆需类型转换,请为此采用non-member函数

class Rational{...}; // 不包括operator*
// non-member
const Rational operator*(const Rational& lhs, const Rational& rhs) {
  return Rational(lhs.numerator() * rhs.numerator(), 
    lhs.denominator() * rhs.denominator());
}
Rational oneHalf(1, 2);
Rational result = 2 * oneHalf;

考虑写出一个不抛异常的swap函数

namespace WidgetStuff{
  template<typename T> 
  class Widget{...}; // 内含swap成员函数

  template<typename T>
  void swap(Widget<T>& a, Widget<T>& b) {
    a.swap(b);
  }
}

实现

尽可能延后变量定义式的出现时间

尽量少做转型动作

如果可以,尽量避免转型,尤其是dynamic_cast
许多程序员相信,转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型,这是错误的观念。
任何一个类型转换(包括通过转型操作而进行的显示转换,或通过编译器完成的隐式转换)往往真的另编译器编译出运行期间执行的代码

class SpecialWindow: public Window {
  public:
    virtual void onResize() {
      Window::onResize();
      // 而不是 static_cast<Window>(*this).onResize();
    }
};

异常安全而努力是值得的

void PrettyMenu::changeBackground(std::iostream& imgSrc) {
  // 互斥器作为并发控制
  lock(&mutex);
  delete bgImage;
  ++imageChanges;
  bgImage = new Image(imgSrc);
  unlock(&mutex);
}

从异常安全性的观点看,这个函数很糟。

  1. 不泄露任何资源:一旦new Image(imgSrc)导致异常,对unlock的调用就绝不会执行,于是互斥器就永远被锁住了
  2. 不允许数据被破坏:如果new Image(imgSrc)抛出异常,bgImage就是指向一个已被删除的对象,imageChanges也已被累加
void PrettyMenu::changeBackground(std::iostream& imgSrc) {
  // 使用对象管理资源
  Lock ml(&mutex);
  Image* pIOrig = bgImage;
  bgImage = new Image(imgSrc);
  delete pIOrig;
  ++imageChanges;
}

将bgImage也交给资源管理类

class PrettyMenu{
  shared_ptr<Image> bgImage;
};
void PrettyMenu::changeBackground(std::iostream& imgSrc) {
  // 使用对象管理资源
  Lock ml(&mutex);
  bgImage.reset(new Image(imgSrc));
  ++imageChanges;
}

还有一个一般化的策略:copy and swap。为你打算修改的对象做出一份副本,然后在副本身上做一切必要修改,若有任何修改动作抛出异常,原对象保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)

透彻了解inline的里里外外

免除函数调用成本,inline只是对编译器的一个申请,不是强制命令

所有对virtual函数的调用也都会使inline落空,因为virtual意味着等待,直到运行期才确定调用哪个函数,而inline意味着执行前,先将调用动作替换为被调用函数的本体
如果编译器无法将你要求的函数inline化,会给你一个警告信息

继承与面向对象设计

确定你的public继承塑模出is-a关系

适用于base class身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也是一个base class对象

避免遮掩继承而来的名称

class Derived: public Base {
  public:
    using Base::mf1; // 让Base class内 名为mf1的所有东西可见
};

using 声明式被放在derived class的public区域:base class内的public名称在public derived class内也应该是public
using声明式会令继承而来的某给定名称之所有同名函数在derived class中都可见

区分接口继承和实现继承

  1. 声明一个pure virtual函数目的是为了让derived class只继承函数接口
    virtual void draw() const = 0;
  2. 声明简朴的(非纯)impure virtual函数的目的,是让derived class继承该函数的接口和缺省实现
    virtual void error(const std::string& msg);
  3. 声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现
    int objectID() const;

绝不重新定义继承而来的non-virtual函数

non-virtual函数都是静态绑定的

绝不重新定义继承而来的缺省参数值

Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;

ps、pc、pr都被声明为pointer-to-shape类型,所有它们的静态类型都是Shape*
所谓的对象的动态类型是指目前所指对象的类型,也就是说,动态类型可以表现出一个对象将会有什么行为
virtual函数是动态绑定,而缺省参数值却是静态绑定

virtual函数的缺省参数值来自base class而非derived class
virtual函数,唯一应该覆写的东西就是动态绑定

通过复合塑模出has-a

public继承意味着,如果D是一种B,对B为真的每一件事情对D也都应该为真

C++用来解析(resolving)重载函数调用的规则是:首先确定这个函数是否是最佳匹配,然后找出最佳匹配函数后才检验其可取用性

模板与泛型编程

了解隐式接口和编译器多态

template<typename T>
void doProcessing(T& w) {
  if (w.size() > 10 && w != someNastyWidget) {
    T temp(w);
    temp.normalize();
    temp.swap(w);
  }
}
  1. w必须支持哪一种接口,是由template中执行于w身上的操作决定的。本例中w的类型T必须支持size、normalize和swap成员函数、copy构造函数、不等比较,这一组表达式便是T必须支持的一组隐式接口
  2. 以不同的template参数具现化function template会导致调用不同的函数,这便是所谓的编译器多态
    类似于哪一个重载函数该被调用(发生在编译期)哪一个virtual函数该被绑定(发生在运行期)

了解typename的双重意义

typename只被用来检验嵌套从属类型名称,其他名称不该有它存在

template<typename C>          // 允许使用typename或class
void f(const C& container,    // 不允许使用typename
  typename C::iterator iter); // 一定要使用typename

typename不可出现在base class list内的嵌套从属类型名称之前,也不可在成员初始化列表中作为base class修饰符

template<>
class MsgSender<CompanyZ> {
  public:
    void sendSecret(const MsgInfo& info){}
}

class定义式最前头的template<>语法象征这既不是template也不是标准class,而是个特化版的MsgSender template,在template实参是CompanyZ时被使用

需要类型转换时请为模板定义非成员函数

唯有non-member函数才有能力在所有实参身上实施隐式类型转换

定制new和delete

new出来的内存前后安排signiture签名
C++要求所有的operator new返回的指针都有适当的对齐(取决于数据类型)

机器学习-周志华_笔记

第一章 绪论

机器学习正是这样一门学科,它致力于研究如何通过计算的手段,利用经验来改善系统自身的性能。如果说计算机科学是研究关于算法的学问,那么可以说机器学习是研究关于学习算法的学问

若预测的是离散值,例如好瓜、坏瓜,此类学习任务称为分类;若预测的是连续值,例如西瓜的成熟度0.95、0.37,此类学习任务称为回归。一般地,预测任务是希望通过对训练集equation进行学习,建立一个从输入空间equation到输出空间equation的映射equation。对二分任务,通常令equationequation;对多分类任务,equation;对回归任务,equation,R为实数集

根据训练数据是否拥有标记信息,学习任务可大致划分为两大类:监督学习和无监督学习,分类和回归是前者的代表,而聚类则是后者的代表

通常假设样本空间中全体样本服从一个未知的分布equation,我们获得的每个样本都是独立地从这个分布上采样获得的,即独立同分布。一般而言,训练样本越多,得到的关于equation的信息越多,这样就越有可能通过学习获得具有强

第二章 模型评估与选择

通常把分类错误的样本数占样本总数的比例称为错误率(error rate),即如果在m个样本中有a个样本分类错误,则错误率equation

equation称为精度(accuracy)

交叉验证法(cross validation)

调参(parameter tuning)

回归任务最常用的性能度量是均方误差(mean squared error)

equation

查准率(precision)、查全率(recall)与F1
挑出的西瓜中有多少比例是好瓜、所有好瓜中有多少比例被挑了出来

第三章 线性模型

给定由d个属性描述的示例equationequationequationequation个属性上的取值,线性模型(linear model)试图学得一个通过属性的线性组合来进行预测的函数,即

equation

equation

如何确定w和b,可试图让均方误差最小化,即equation

均方误差对应了常用的欧几里得距离或简称欧式距离,基于均方误差最小化来进行模型求解的方法称为最小二乘法,求解w和b使上式最小化的过程,称为线性回归模型的最小二乘参数估计

线性判别分析(Linear Discriminant Analysis,简称LDA),是一种经典的线性学习方法,给定训练样例集,设法将样例投影到一条直线上,使得同类样例的投影点尽可能接近,异类样例的投影点尽可能远离

第四章 决策树

在对特征排序前先设想一下,对某一个特征进行决策时,我们肯定希望分类后样本的纯度越高越好,也就是说分支结点的样本尽可能属于同一类别。
所以在选择根节点的时候,我们应该选择能够使得“分支结点纯度最高”的那个特征。在处理完根节点后,对于其分支节点,继续套用根节点的**不断递归,这样就能形成一颗树。

第五章 神经网络

有两种策略用来缓解BP网络的过拟合,第一种策略是早停,另一种策略是正则化

负梯度方向是函数值下降最快的方向,因此梯度下降法就是沿着负梯度方向搜索最优解

第六章 支持向量机

第七章 贝叶斯分类器

贝叶斯定理

equation

极大似然估计,根据实验结果对分布参数作最大概率的估计

第八章 集成学习

通过构建并结合多个学习器来完成学习任务,有时也被称为多分类器系统

第九章 聚类

K均值聚类

密度聚类DBSCAN

层次聚类,形成树形的聚类结构

第十章 降维与度量学习

k近邻学习
给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个邻居的信息来进行预测

低维嵌入(embedding)

主成分分析(Principal Component Analysis,简称PCA)

double free

// This file is a "Hello, world!" in C++ language by GCC for wandbox.
#include <iostream>
#include <cstdlib>

class RunTest {
public:
    RunTest(): ptr(new int){}
    ~RunTest() { delete ptr; }
private:
    int* ptr;
};

int main()
{
    RunTest rt1;
    RunTest rt2 = rt1;
    
    std::cout << "Hello, Wandbox!" << std::endl;
}

// GCC reference:
// https://gcc.gnu.org/

_20180122210546

replace a newline(\n) using sed

Use this solution with GNU sed:

sed ':a;N;$!ba;s/\n/ /g' file
This will read the whole file in a loop, then replaces the newline(s) with a space.

Explanation:

Create a label via :a.
Append the current and next line to the pattern space via N.
If we are before the last line, branch to the created label $!ba ($! means not to do it on the last line as there should be one final newline).
Finally the substitution replaces every newline with a space on the pattern space (which is the whole file).
Here is cross-platform compatible syntax which works with BSD and OS X's sed (as per @Benjie comment):

sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' file

C++模板的偏特化与全特化

Template中的class和typename

template<class T> class Test; 
template<typename T> class Test; 

在模板引入C++后最初定义模板的方式是用关键字class,表明紧跟在后面的符号是一个类型,后来为了避免在class在定义类的时候带来混淆,又引入了typename关键字。

在模板定义语法中关键字class与typename的作用完全一样,但是在使用嵌套依赖类型(nested depended name)时只能使用typename关键字。

typedef typename T::NestType NT;

这个时候typename的作用就是告诉编译器,typename后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有typename,编译器没有任何办法知道T::NestType是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。

non-type template 参数

template<class T, class Ref, size_t BufSiz = 0>

class template拥有non-type template 参数

模板的声明

// 类模板
template <class T1, class T2>
class A{
    T1 data1;
    T2 data2;
};

// 函数模板
template <class T>
T max(const T lhs, const T rhs){   
    return lhs > rhs ? lhs : rhs;
}

模板的特化

特化必须在同一命名空间下进行,可以特化类模板也可以特化函数模板,但类模板可以偏特化和全特化,而函数模板只能全特化。 模板实例化时会优先匹配”模板参数”最相符的那个特化版本。

全特化

通过全特化一个模板,可以对一个特定参数集合自定义当前模板,类模板和函数模板都可以全特化。全特化的模板参数列表应当是空的,并且应当给出”模板实参”列表:

// 全特化类模板
template <>
class A<int, double>{
    int data1;
    double data2;
};

// 函数模板
template <>
int max(const int lhs, const int rhs){   
    return lhs > rhs ? lhs : rhs;
}

注意类模板的全特化时在类名后给出了”模板实参”,但函数模板的函数名后没有给出”模板实参”。 这是因为编译器根据int max(const int, const int)的函数签名可以推导出来它是T max(const T, const T)的特化。

但是这一过程有时是有歧义的:

template <class T>
void f(){ T d; }

template <>
void f(){ int d; }

此时编译器不知道f()是从f<T>()特化来的,编译时会有错误:

error: no function template matches function template specialization 'f'

这时我们便需要显式指定”模板实参”:

template <class T>
void f(){ T d; }

template <>
void f<int>(){ int d; }

偏特化

类似于全特化,偏特化也是为了给自定义一个参数集合的模板,但偏特化后的模板需要进一步的实例化才能形成确定的签名,但值得注意的是函数模板不允许偏特化。偏特化也是以template来声明的,需要给出剩余的”模板形参”和必要的”模板实参”。例如:

template <class T2>
class A<int, T2>{
    ...
};

所谓偏特化并不一定是对template参数的T1,T2指定某个参数值,其意思是提供另一份template的定义式,而其本身仍为templatized。

template<typename T>
class c{...}; // 这个泛化版本允许接受T为任何类型

template<typename T>
class c<T*> {...};
// 这个特化版本仅适用于“T为原生指针”的情况
// “T为原生指针”便是“T为任何类型”的一个更进一步的条件限制

函数模板是不允许偏特化的,下面的声明会编译错:

template <class T1, class T2>
void f(){}

template <class T2>
void f<int, T2>(){}

但函数允许重载,声明另一个函数模板即可替代偏特化的需要:

template <class T2>
void f(){}              // 注意:这里没有"模板实参"

STL Tutorial

Introduction of Templates

// Function Template
template <typename T> 
T square (T x) {
  return x * x;
}

int main() {
  cout << square<int>(5) << endl;
  cout << square<double>(5.5) << endl;
}
// Class Template
template <typename T> 
T square (T x) {
  return x * x;
}

template <typename T> 
class BoVector {
  T arr[1000];
  int size;
public:
  BoVector() : size(0) {}
  void push(T x) { arr[size] = x; size++; }
  T get(int i) const { return arr[i]; }
  int getSize() const { return size; }
  void print() const { for(int i = 0; i < size; i++) { cout << arr[i] << endl; } }
};

template<typename T>
BoVector<T> operator* (const BoVector<T>& rhs1, const BoVector<T>& rhs2) {
  BoVector<T> ret;
  for (int i = 0; i < rhs1.getSize(); i++) {
    ret.push(rhs1.get(i) * rhs2.get(i));
  }

  return ret;
}

int main() {
  BoVector<int> bv;
  bv.push(2);
  bv.push(5);

  bv = square(bv);
  bv.print();
}

Introduction of STL : Overview

Standard Template Library

  1. Algorithms
  2. Containers
    2017-04-24-11 11 11
using namespace std;

// dynamic array allocated on the heap 
// the size of it is not fixed
vector<int> vec;
vec.push_back(4);
vec.push_back(1);
vec.push_back(8); // vec: {4, 1, 8}

// half-open : [begin, end) 前闭后开
// vector中的对象是没有命名的,可以按vector中对象的位置来访问它们
vector<int>::iterator itr1 = vec.begin();
vector<int>::iterator itr2 = vec.end();

// iterator just like the pointer
for (vector<int>::iterator itr = itr1; itr != itr2; ++itr) { cout << *itr << endl; }

sort(itr1, itr2); // vec : {1, 4, 8}

2017-04-24-11 30 34

Introduction of Containers

  1. Sequence Containers (array and linked list) [线性]:
  • vector, deque(双端队列), list, forward list, array
  1. Associative Containers (binary tree)
  • set, multiset
  • map, multimap
  1. Unordered Containers (hash table)
  • Unordered set/multiset
  • Unordered map/multimap
// STL Headers

#include <vector>
#include <deque>
#include <list>
#include <set>  // set and multiset
#include <map>  // map and multimap
#include <unordered_set> // unordered set/multiset
#include <unordered_map>  // unordered map/multimap
#include <iterator>
#include <algorithm>
#include <numeric>  // some numeric algorithm
#include <functional>

Sequence Containers

/*
 * Vector
 */
vector<int> vec; // vec.size() = 0;
vec.push_back(4);
vec.push_back(1);
vec.push_back(8);

// Vector specific operations
cout << vec[2];  // 8 (no range check)
cout << vec.at(2);  // 8 (throw range_error exception of out of range)

for (int i = 0; i < vec.size(); i++) cout << vec[i] << " ";

// usually faster than random access
for (vector<int>::iterator itr = vec.begin(); itr != vec.end(); ++itr) cout << *itr << " ";

for (it : vec)  // C++ 11
  cout << it << " ";

// Vector is dynamically allocated contiguous array in memory
int* p = &vec[0];  p[2] = 6;
// Common member functions of all containers

if (vec.empty()) cout << "Not possible.\n";

cout << vec.size();

vector<int> vec2(vec); // Copy construct, vec2 : {4, 1, 8}
vector<int> vec3(3, 0); // vec3 : {0, 0, 0}

vec.clear();  // Remove all items in vec; vec.size() == 0

vec2.swap(vec); // vec2 becomes empty, and vec has 3 items
/* Properties of Vector:
 * 1. fast insert/remove at the end: O(1)
 * 2. slow insert/remove at the begining or in the middle: O(n)
 * 3. slow search: O(n)
 */
/*
 * Deque
 */
deque<int> deq = {4, 6, 7};
deq.push_front(2);
deq.push_back(3);

cout << deq[1] << endl;
/* Properties of Deque:
 * 1. fast insert/remove at the begining and the end: O(1)
 * 2. slow insert/remove in the middle: O(n)
 * 3. slow search: O(n)
 */
/*
 * List (double linked)
 */
list<int> mylist = {5, 2, 9};
mylist.push_back(6);
mylist.push_front(4); // mylist : {4, 5, 2, 9, 6}

list<int>::iterator itr = find(mylist.begin(), mylist.end(), 2);
mylist.insert(itr, 8); // mylist : {4, 5, 8, 2, 9, 6}

itr++;  // itr -> 9
mylist.erase(itr); // mylist : {4, 5, 8, 2, 6}

// mylist2切片后添加到mylist1
mylist1.splice(itr, mylist2, itr_a, itr_b);  // O(1)
/* Properties of List:
 * 1. fast insert/remove at any place: O(1)
 * 2. slow search: O(n), much slower than vector (cache missing)
 * 3. no random access, no [] operator
 */
/*
 * Array 
 */
int a[3] = {3, 4, 5};
// element type and size
array<int, 3> a = {3, 4, 5};
a.begin();
a.end();
a.size();
a.swap();

array<int, 4> b = {1, 3, 4, 5};
/* Properties of Array:
 * 1. the size can not be changed
 * 2. two array of integer may have different type
 */

2017-04-24-14

Associative Containers

Set or Multiset

2017-04-24

/*
 * 1. Is always sorted
 * 2. No push_back(), push_front()
 */
/*
 * Set - No duplicates 
 */
set<int> myset;
myset.insert(3);  // myset : {3}
myset.insert(1);  // myset : {1, 3}
myset.insert(7);  // myset : {1, 3, 7}, sorted, O(log(n))

set<int>::iterator it;
it = myset.find(7);  // O(log(n)), it points to 7
                             // Sequence containers don't even have find() member function

pair<set<int>::interator, bool> ret;
ret = myset.insert(3); // no new element inserted
if (ret.second == false) it = ret.first; // it now points to element 3

myset.insert(it, 9); // myset : {1, 3, 7, 9}, just provide hint, may O(log(n)) => O(1)
                             // it points to 3
myset.erase(it); // myset : {1, 7, 9}
myset.erase(7); // myset : {1, 9}, remove an item by its value
// multiset is a set that allows duplicated items
multiset<int> myset;

// set/multiset: value of the elements cannot be modified
*it = 10; // *it is read-only
/* Properties:
 * 1. Fast search: O(log(n))
 * 2. Traversing is slow (compared to vector & deque)
 * 3. No random access, no [] operator
 */

Map or MultiMap

2017-04-24_15-46-14

/*
 * map - No duplicated key
 */
map<char, int> mymap;
mymap.insert ( pair<char, int>('a', 100) );
mymap.insert ( make_pair('z', 200) );

map<char, int>::iterator it = mymap.begin();
mymap.insert(it, pair<char, int>('b', 300)); // it -> just a hint

it = mymap.find('z'); // O(log(n))

for (it = mymap.begin(); it != mymap.end(); ++it) cout << (*it).first << " => " (*it).second << endl;
// multimap is a map that allows duplicated keys
multimap<char, int> mymap;

// map/multimap: keys cannot be modified 
// type of *it : pair<const char, int>
(*it).first = 'd'; // Error, *it is read-only

Unordered Associative Container (C++ 11)

2017-04-24_16-12-20

Implementation[实现] of Unordered Containers
2017-04-24_16-24-30

/*
 * unordered set
 */
unordered_set<string> myset = {"red", "green", "blue"};
unordered_set<string>::const_iterator itr = myset.find("green"); // O(1)
if (itr != myset.end()) // Important check
 cout << * itr << endl;
myset.insert("yellow"); // O(1)

vector<string> vec = {"purple", "pink"};
myset.insert(vec.begin(), vec.end());

// Hash table specific APIs
cout << "load_factor = " << myset.load_factor() << endl;
string x = "red";
cout << x << " is in bucket #" << myset.bucket(x) << endl;
cout << "Total bucket #" << myset.bucket_count() << endl;
// unordered multiset : allows duplicated elements
// unordered map : unoredred set of pairs
// unordered multimap: allows duplicated keys

// hash collision => performance degrade[降级]

/* Properties of Unordered Containers:
 * 1. Fastest search/insert at any place: O(1)
 * 2. Unordered set/multiset: element value cannot be changed
 *     Unordered map/multimap: element key cannot be changed
 */
/*
 * Associative Array - map and unordered map
 */
unordered_map<char, string> day = {{'S', "Sunday"}, {'M', "Monday"}};

cout << day['S'] << endl; // No range check
cout << day.at('S') << endl; // Has range check

vector<int> vec = {1, 2, 3};
vec[5] = 5; // Compile Error

day['W'] = "Wednesday"; // Insert {'W', "Wednesday"}
day.insert(make_pair('F', "Friday")); 

day.insert(make_pair('M', "MONDAY")); // Fail to modify, it's an unordered map
day['M'] = "MONDAY"; // Succeed to modify
void foo(const unordered_map<char, string>& m) {
  // m['S'] = "SUNDAY"; 
  // cout << m['S'] << endl;  not compile

  auto itr = m.find('S');
  if (itr != m.end()) cout << *itr << endl;
}
foo(day);
/*
 * Container Adapter
 * 1. stack: LIFO, push(), pop(), top()
 * 2. queue: FIFO, push(), pop(), front(), back()
 * 3. priority queue: first item always has the greatest priority 
 *                       push(), pop(), top()
 */
/*
 * Another way of categorizing containers:
 * 1. Array based containers: vector, dequeue
 *  2. Node based containers: list + associative containers + unordered containers
 */
vector<int> vec = {1, 2, 3, 4};
int *p = &vec[2];
vec.insert(vec.begin(), 0);
cout << *p << endl;  // 2, or ?, or crash 指针失效
                              // 基于节点的容器不存在此问题

Iterators and Algorithms

/*
 * Iterators
 */
// 1. Random Access Iterator : vector, deque, array
vector<int> itr;
itr = itr + 5; 
itr = itr - 4;
if (itr2 > itr1) ...
++itr;  // faster than itr++
--itr;

// 2. Bidirectional[双向] Iterator : list, set/multiset, map/multimap
list<int> itr;
++itr;
--itr;

// 3. Forward[前向] Iterator : forward_list
forward_list<int> itr;
++itr;
// Unordered Containers provide at least forward iterators

// 4. Input Iterator : read and process values while iterating forward
int x = *itr;

// 5. Output Iterator : output values while iterating forward
*itr = 100;
// Every container has a iterator and a const_iterator
set<int>::iterator itr;
set<int>::const_iterator citr; // Read_Only access to container elements

set<int> myset = {2, 4, 5, 1, 9};
for (citr = myset.begin(); citr != myset.end(); ++citr) {
  cout << *citr << endl;
  // *citr = 3;
}
// const_iterator
for_each(myset.cbegin(), myset.cend(), MyFunction); // Only in C++ 11

// Iterator Functions:
advance(itr, 5); // Move itr forward 5 spots. itr += 5;
distance(itr1, itr2); // Measure the distance between itr1 and itr2
/*
 * Iterator Adaptor (Predefined Iterator)
 * 1. Insert iterator
 * 2. Stream iterator
 * 3. Reverse iterator
 * 4. Move iterator (C++ 11)
 */
// 1. Insert Iterator:
vector<int> vec1 = {4, 5};
vector<int> vec2 = {12, 14, 16, 18};
vector<int>::iterator it = find(vec2.begin(), vec2.end(), 16);
insert_iterator<vector<int>> i_itr(vec2, it);
copy(vec1.begin(), vec1.end(),  //source
    i_itr);                                      //destination
  // vec2 : {12, 14, 4, 5, 16, 18}
// Other insert iterators : back_insert_iterator, front_insert_iterator

// 2. Stream Iterator:
vector<string> vec4;
copy(istream_iterator<string>(cin), istream_iterator<string>(), back_inserter(vec4));

copy(vec4.begin(), vec4.end(), ostream_iterator<string>(cout, " "));

copy(istream_iterator<string>(cin), istream_iterator<string>(), ostream_iterator<string>(cout, " "));

// 3. Reverse Iterator:
vector<int> vec = {4, 5, 6, 7};
reverse_iterator<vector<int>::iterator> ritr;
for (ritr = vec.rbegin(); ritr != vec.rend(); ritr++) cout << *ritr << endl;
/*
 * Algorithms - mostly loops
 */
vector<int> vec = {4, 2, 5, 1, 3, 9};
vector<int>::iterator itr = min_element(vec.begin(), vec.end()); 

// ranges in a half-open way : [begin, end)
sort(vec.begin(), itr);
reverse(itr, vec.end());

vector<int> vec2(3);
copy(itr, vec.end(), //source
    vec2.begin());  //destination
// vec2 needs to have at least space for 3 elements

vector<int> vec3;
copy(itr, vec.end(), back_inserter(vec3)); // Inserting instead of overwriting
                              // back_insert_interator  Not efficient
vec3.insert(vec3.end(), itr, vec.end()); // Efficient and safe


// Algorithm with function
bool isOdd(int i) {
  return i%2;
}

int main() {
  vector<int> vec = {2, 4, 5, 9, 2};
  vector<int>::iterator itr = find_if(vec.begin(), vec.end(), isOdd);
                                           // itr -> 5
}

// Algorithm with native C++ array
int arr[4] = {6, 3, 7, 4};
sort(arr, arr + 4);

Non-modifying Algorithms

/*
 * STL Algorithm Walkthrough:
 * Non-modifying Algorithms
 * count, min and max, compare, linear search, attribute
 */

vector<int> vec = {9, 60, 90, 8, 45, 87, 90, 69, 69, 55, 7};
// 1. Counting
int n = count(vec.begin(), vec.end(), 69);  //2
// C++ 11 Lambda Function:
int m = count_if(vec.begin(), vec.end(), [](int x) { return x < 10; }); //3

// 2. Min and Max
itr = max_element(vec.begin() + 2, vec.end()); //90
itr = max_element(vec.begin(), vec.end(), [](int x, int y) { return (x%10) < (y%10)}); //9

// 3. Linear Searching (used when data is not sorted or use binary search) 
// Return the first match
itr = find(vec.begin(), vec,end(), 55);
itr = find_if(vec.begin(), vec.end(), [](int x) {return x > 80;});
itr = find_if_not(vec.begin(), vec.end(), [](int x) {return x > 80;});
itr = search_n(vec.begin(), vec.end(), 2, 69); //Consecutive 2 items of 69

// Search subrange
vector<int> sub = {45, 87, 90};
itr = search(vec.begin(), vec.end(), sub.begin(), sub.end());
       // search first subrange
itr = find_end(vec.begin(), vec.end(), sub.begin(), sub.end());
       // search last subrange

// Search any_of
vector<int> items = {87, 69};
itr = find_first_of(vec.begin(), vec.end(), items.begin(), items.end());
       // Search any one of the item in items

// 4. Comparing Ranges
if (equal(vec.begin(), vec.end(), vec2.begin())) cout << "vec and vec2 are same" << endl;

// 5. Check Attributes
is_sorted(vec.begin(), vec.end());
itr = is_sorted_until(vec.begin(), vec.end());

is_heap(vec.begin(), vec.end());

all_of(vec.begin(), vec.end(), [](int x) {return x > 80;});
any_of(vec.begin(), vec.end(), [](int x) {return x > 80;});
none_of(vec.begin(), vec.end(), [](int x) {return x > 80;});

Modifying Algorithms

/*
 * Algorithm Walkthrough:
 *     Value-changing Algorithm - Changes the element values
 *     copy, move, transform, swap, fill, replace, remove
 */
vector<int> vec = {9, 60, 70, 8, 45, 87, 90};
vector<int> vec2 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; //11 items
vector<int>::iterator itr, itr2;
pair<vector<int>::iterator, vector<int>::iterator> pair_of_itr;

// Copy
copy(vec.begin(), vec.end(),  // Source
    vec2.begin());                     // Destination. vec2.size() > vec.size()

copy_if(vec.begin, vec.end(),
    vec2.begin(),
    [](int x) { return x > 80; });
// vec2 : {87, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0}

copy_n(vec.begin(), 4, vec2.begin());
// vec2 : {9, 60, 70, 8, 0, 0, 0, 0, 0, 0, 0}

copy_backward(vec.begin(), vec.end(),
    vec2.end());
// vec2 : {0, 0, 0, 0, 9, 60, 70, 8, 45, 87, 90}

unique(vec.begin(), vec.end()); // Remove consecutive[连续] equal elements
/*
 * Order-Changing Algorithms
 *     - reverse, rotate, permute, shuffle
 * They changes the order of elements in container, but not necessarily the elements themselves.
 */

Sorting

/*
 * Sorting in STL
 */
// Sorting algorithm requires random access iterators : 
//     - vector, deque, container array, native array

vector<int> vec = {9, 1, 10, 2, 45, 3, 90, 4, 9, 5, 8};
sort(vec.begin(), vec.end()); // sort with operator < 
// vec : 1 2 3 4 5 8 9 9 10 45 90

bool lsb_less(int x, int y) {
  return (x%10) < (y%10);
}
sort(vec.begin(), vec.end(), lsb_less);
// vec : {10, 90, 1, 2, 3, 4, 45, 5, 8, 9, 9}
/*
 * Heap Algorithms
 * 
 * Heap: 
 * 1. First element is always the largest 
 * 2. Add/remove takes O(log(n)) time
 */

vector<int> vec = {9, 1, 10, 2, 45, 3, 90, 4, 9, 5, 8};

make_heap(vec.begin(), vec.end());
// vec : {90, 45, 10, 9, 8, 3, 9, 4, 2, 5, 1}

// Remove the largest element
pop_heap(vec.begin(), vec.end()); // 1. Swap vec[0] with last item vec[size - 1]
         // 2.Heapify [vec.begin(), vec.end() - 1] 
         // vec : {45, 9, 10, 4, 8, 3, 9, 1, 2, 5, 90}

vec.pop_back(); // Remove the last item (the largest one)
// vec : {45, 9, 10, 4, 8, 3, 9, 1, 2, 5}

// Add a new element
vec.push_back(100);
push_heap(vec.begin(), vec.end()); // Heapify the last item in vec
// vec : {100, 45, 9, 10, 4, 8, 3, 9, 1, 2, 5}

// Heap Sorting
sort_heap(vec.begin(), vec.end());
// vec : {1, 2, 3, 4, 5, 8, 9, 9, 10, 45, 100}
// Note : sort_heap can only work on a heap

Sorted Data Algorithms [有序数据]

/*
 * Sorted Data Algorithms
 *   - Algorithms that require data being pre-sorted
 *   - binary_serach, merge, set operations
 */

vector<int> vec = {8, 9, 9, 9,45, 87, 90};

// 1. Search Elements
bool found = binary_search(vec.begin(), vec.end(), 9);

vector<int> s = {9, 45, 66};
bool found = includes(vec.begin(), vec.end(),  // Range #1
    s.begin(), s.end());                       // Range #2
// Return true if all elements of s is included in vec
// Both vec and s must be sorted

// Search Position
itr = lower_bound(vec.begin(), vec.end(), 9);
// Find the first position where 9 could be inserted and still keep the sorting

itr = upper_bound(vec.begin(), vec.end(), 9);

pair_of_itr = equal_range(vec.begin(), vec.end(), 9);
// Return both first and last position

// 2. Merge
vector<int> vec = {8, 9, 9, 10};
vector<int> vec2 = {7, 9, 10};
merge(vec.begin(), vec.end(),          // Input Range #1
    vec2.begin(), vec2.end(),             // Input Range #2
    vec_out.begin());
    // Nothing is dropped, all duplicates are kept
// vec_out : {7, 8, 9, 9, 10, 10}

vector<int> vec = {1, 2, 3, 4, 1, 2, 3, 4, 5};  // Both part of vec are already sorted
inplace_merge(vec.begin(), vec.begin() + 4, vec.end());
// vec : {1, 1, 2, 2, 3, 3, 4, 4, 5}  -  One step of merge sort

// 3. Set operations
vector<int> vec = {8, 9, 9, 10};
vector<int> vec2 = {7, 9, 10};
vector<int> vec_out[5];
set_union(vec.begin(), vec.end(),
    vec2.begin(), vec2.end(),
    vec_out.begin());
    // If X is in both vec and vec2, only one X is kept in vec_out
// vec_out : {7, 8, 9, 9, 10}

// set_intersection[交集], set_difference[差集] 

string

int main() {
  // Constructors
  string s1("Hello");
  string s2("Hello", 3);  // size, s2 : Hel
  string s3(s1, 2);  // start, s3: llo
  string s4(s1, 2, 2);  // start, size s4 : ll
  string s5(5, 'a');  // s5 : "aaaaa"
  string s6({'a', 'b', 'c'});  // s6 : "abc"

  // Size
  s1 = "Goodbye";
  s1.size(); s1.length(); // both return 7
  s1.capacity();  // size of storage space currently allocated to s1, >s1.size()
  s1.reserve(100); // allocate, 100 chars
  s1.reserve(5); // s1 : Goodbye, s1.size() ==7, s1.capacity() == 7
// reserve does not change the content of s1 and does not change the size
  s1.shrink_to_fit();  // shrink the capacity to hold the content

  s1.resize(9);  // s1: Goodbye\0\0
  s1.resize(12, 'x');
  s1.resize(3);  // s1: Goo
}
// Single Element Access
s1 = "Goodbye";
s1[2];  // 'o'
s1[2] = 'x';  // Goxdbye
s1.at(2) = 'y';  // Goydbye
s1.at(20);  // throw exception of out_of_range
s1[20];  // undefined behavior

s1.front(); // 'G'  reference
s1.back(); // 'e'
s1.push_back('z');
s1.pop_back();
s1.begin();  // iterator
s1.end();
// Like vector, but string doesn't have push_front() and pop_front()

string s3(s1.begin(), s1.begin() + 3); // s3 : Goo

// Ranged Access
// assign, append, insert, replace
string s2 = "Dragon land";

s1.assign(s2); // s1 = s2, assign[分配]
s1.assign({'a', 'b', 'c'});

s1.append(" def"); // s1 : abc def
s1.insert(2, "mountain", 4); // s1 : abmounc def
s1.erase(1, 4); // position size

// 一个参数size,两个参数position, size
// Member Function Algorithms: copy, find, compare
s1 = "abcdefg";
char buf[20];
size_t len = s1.copy(buf, 3); // buf : abc  len == 3
// buf[] is not terminated -> \0

// BAD : size, position
len = s1.copy(buf, 4, 2); // buf :  cdef  len == 4

s1 = "If a job is worth doing, it's worth doing well";
size_t found = s1.find("doing");  // found == 17
found = s1.find("doing", 20); // found == 35

found = s1.find_first_of("doing");  // found == 6, o, any of

s1.compare(s2); // return positive if (s1 > s2); negative if (s1 < s 2); 0 if (s1 == s2)

string ss = s1 + s2;
// Non-member functions
cout << s1 << endl;
cin >> s1;
getline(cin, s1);  // default delimeter of '\n'
getline(cin, s1, ';');  // delimeter is ';'

// convert a number into a string
s1 = to_string(0xa4); // s1 : 164

// convert a string into a number
s1 = "190";
int i = stoi(s1); // i : 190

s1 = "190 monkeys";
size_t pos;
i = stoi(s1, &pos); // i : 190  pos == 3

s1 = "a monkey";
i = stoi(s1, &pos);  // exception of invalid_argument
i = stoi(s1, &pos, 16); // hex, i == 10
// String and Algorithms
s1 = "Variety is the spice of life.";
int num = count(s1.begin(), s1.end(), 'e'); //4
num = count_if(s1.begin(), s1.end(), [](char c){ return (c <= 'e' && c >= 'a'); }); //6

s1 = "Goodness is better than beauty.";
string::iterator itr = search_n(s1.begin(), s1.begin() + 20, 2, 's'); // itr -> fisrt 's'
s1.erase(itr, itr + 5);

stream

int main() {
  cout << "Hello" << endl;
  // cout : a global object of ostream (typedef basic_ostream<char> ostream)
  // <<:       ostream& ostream::operator<< (string v);
  // endl:     '\n' + flush
  
  {
    // What is stream? Serial IO Interface to external devices (file, stdin/stdout, network, etc...)
    ofstream of("Mylog.txt"); // create if not exist
    of << "Experience is the mother of wisdom" << endl;
    of << bitset<8>(14) << endl; // 00001110
    of << complex<int>(2, 3) << endl; // (2, 3)
  } // RAII

  {
    ofstream of("Mylog.txt", ofstream::app); // move the output pointer to the end of the file 
    of << "Experience is the mother of wisdom" << endl;
  } 

  {
    ofstream of("Mylog.txt", ofstream::in | ofstream::out); 
    of.seekp(10, ios::beg); //move the output pointer 10 chars after begin
    of << "12345" << endl;  // overwrite 5 chars
    of.seekp(-5, ios::end);
    of << "nothing gained" << endl;
    of.seekp(-5, ios::cur);
  } 

  ifstream inf("Mylog.txt");
  int i;
  inf >> i; // read one word
}
ostream& endl (ostream& sm) {
  sm.put('\n');
  sm.flush();
  return sm;
}

ostream& ostream::operator<<(ostream& (*func)(ostream&)) {
  return (*func)(*this);
}

cout << ends; // '\0'
cout << flush; 
strcut Dog {
  int age_;
  string name_;
};

istream& operator>>(istream& sm, Dog&d) {
  sm >> d.age_;
  sm >> d.name_;
  return sm;
}

ostream& operator<<(ostream& sm, const Dog& d) {
  sm << "My name is " << d.name_ << " and my age is " << d.age_ << endl;
  return sm;
}

int main() {
  Dog d{2, "Bob"};
  cout << d;

  cin >> d;
  cout << d;
}

元编程初体验之TypeList

typelist和普通的list相似,只不过全是对类型的操作,而且是在编译期,第一次体验到对模板技法的应用,基本全是递归,拗口难懂,写完看着还差不多像个样子

#include <iostream>
#include <cxxabi.h>

struct NullType {};

/*
 * TypeList
 * */
template <typename...> struct TypeList;

template <typename Head, typename... Tail>
struct TypeList<Head, Tail...> {
    typedef Head head;
    typedef TypeList<Tail...> tails;
};

template <>
struct TypeList<> {
    typedef NullType head;
};

/*
 * length
 * */
template <typename> struct Length;

template <typename Head, typename... Tail>
struct Length<TypeList<Head, Tail...>> {
    enum { value = Length<TypeList<Tail...>>::value + 1 };
};

template <>
struct Length<TypeList<>> {
    enum { value = 0 };
};

/*
 * IndexOf
 * */
template <typename, typename> struct IndexOf;

template <typename Head, typename... Tail, typename T>
struct IndexOf<TypeList<Head, Tail...>, T> {
    using Result = IndexOf<TypeList<Tail...>, T>;
    enum { value = Result::value == -1 ? -1 : Result::value + 1 };
};

template <typename... Tail, typename T>
struct IndexOf<TypeList<T, Tail...>, T> {
    enum { value = 0 };
};

template <typename T>
struct IndexOf<TypeList<>, T> {
    enum { value = -1 };
};

/*
 * TypeAt
 * */
template <typename, size_t> struct TypeAt;

template <typename Head, typename... Tail, size_t i>
struct TypeAt<TypeList<Head, Tail...>, i> {
    static_assert(i < sizeof...(Tail), "i beyond the scope");
    typedef typename TypeAt<TypeList<Tail...>, i - 1>::type type;
};

template <typename Head, typename... Tail>
struct TypeAt<TypeList<Head, Tail...>, 0> {
    typedef Head type;
};

/*
 * Append
 * */
template <typename, typename> struct Append;

template <typename... TL, typename T>
struct Append<TypeList<TL...>, T> {
    typedef TypeList<TL..., T> Result;
};

template <typename... TL1, typename... TL2>
struct Append<TypeList<TL1...>, TypeList<TL2...>> {
    typedef TypeList<TL1..., TL2...> Result;
};

/*
 * Erase
 * */
template <typename> struct Erase;

template <typename Head, typename... Tail>
struct Erase<TypeList<Head, Tail...>> {
    typedef TypeList<Tail...> Result;
};

template <>
struct Erase<TypeList<>> {
    typedef NullType Result;
};


int main() {
    using namespace std;

    using TL1 = TypeList<int, char, double>;

    cout << Length<TL1>::value << endl;

    cout << IndexOf<TL1, char>::value << endl;

    cout << abi::__cxa_demangle(typeid(TypeAt<TL1, 0>::type).name(), nullptr, nullptr, nullptr) << endl;

    using AppendType1 = Append<TL1, int>::Result;
    cout << Length<AppendType1>::value << endl;
    using TL2 = TypeList<string, int>;
    using AppendType2 = Append<TL1, TL2>::Result;
    cout << Length<AppendType2>::value << endl;

    using EraseType = Erase<TL1>::Result;
    cout << abi::__cxa_demangle(typeid(EraseType).name(), nullptr, nullptr, nullptr) << endl;
}

深入浅出Node.js_Notes

CommonJS的模块规范

/*
 * exports对象用于导出当前模块的方法或者变量,并且它是唯一的导出出口
 * 在模块中,还存在一个module对象,它代表模块本身,而exports是module的属性
 * 在Node中,一个文件就是一个模块,将方法挂载在exports对象上作为属性即可定义导出的方式
 * 在Node中,每个文件模块都是一个对象 
    function Module(id, parent) {this.id = id; this.exports = {}, this.parent = parent; ...}
 */

// 1. 模块引用
var math = require('math');

// 2. 模块定义
// math.js
exports.add = function() {
  var sum = 0,
    i = 0,
    args = arguments,
    l = args.length;
  while(i < l) sum += args[i++];
 
  return sum;
};
// program.js
var math = require('math');
exports.increment = function(val) {
  return math.add(val, 1);
}

// 3. 模块标识
/*
  模块标识就是传递给require()方法的参数,CommonJS构建的这套模块导出和引入机制使得用户完全不必考虑变量污染
  命名空间等方案与之相比相形见绌
 */

JavaScript模块的编译

每个模块文件中存在着require、exports、module这3个变量,每个模块中还有__filename、__dirname这两个变量的存在

在编译的过程中,Node对获取的JavaScript文件内容进行了头尾包装。在头部添加了(function (exports, require, module, __filename, __dirname){\n, 在尾部添加了\n};。
一个正常的JavaScript文件会被包装成如下的样子:

(function (exports, require, module, __filename, __dirname) {
  var math = require('math');
  exports.area = function (radius) {
    return Math.PI * radius * radius;
  };
});

这样每个模块文件之间都进行了作用域隔离。包装之后的代码会通过vm原生模块的runInThisContext()方法执行(类似eval,只是具有明确上下文,不污染全局),返回一个具体的function对象。最后,将当前模块对象的exports属性、require方法、module(模块对象自身),以及在文件定位中得到的完整文件路径和文件目录作为参数传递给这个function()执行。

关于exports和module.exports

exports是在模块内部指向module.exports的一个引用
module exports

关于异步I/O

我们时常提到的node是单线程的,这里的单线程仅仅只是JavaScript执行在单线程中罢了。在Node中,无论是*nix还是Windows平台,内部完成I/O任务的另有线程池

2017-04-27_17-19-21

线程池与阻塞I/O模拟异步I/O

完成整个异步I/O环节的有事件循环、观察者和请求对象等

process.nextTick()

每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick时取出执行
定时器中采用红黑树的操作事件复杂度为O(log(n)), nextTick()的时间复杂度为O(1)

异步编程

难点

难点1 : 异常处理
Node在处理异常上形成了一种约定,将异常作为回调函数的第一个实参传回,如果为空值,则表明异步调用没有异常抛出

var async = function(callback) {
  process.nextTick() {
    var results = something;
    if (error) {
      return callback(error);
    }
    callback(null, results);
  };
};

async(function (err, results) {
  // TODO
});

难点2 : 函数嵌套过深

难点3 : 阻塞代码

难点4 : 多线程编程

难点5 : 异步转同步

Modern C++

C++11

vector<int> v = {3, 4, 1, 9}; // Calling initializer_list constructor

// auto type
for (auto it = vec.begin(); it != vec.end(); ++it) {
  cout << (*it) << endl;
}
auto a = 6; // integer
auto b = 9.6; // double

// foreach
for (int i : v) {  // works on any class that has begin() and end()
  cout << i ;    // readonly access
}
for (auto& i : v) {
  i++;  // changes the values in v
}

// nullptr
void foo(int i) {cout << "foo_int" << endl;}
void foo(char* pc) {cout << "foo_char*" << endl;}
int main() {
  foo(NULL);  // 歧义
  foo(nullptr); // call foo(char*)
}

// enum class
// c++ 03
enum apple {green_a, red_a};
// c++ 11
enum class apple {green, red};
apple a = apple::green;

// static_assert
// run-time assert
assert(myPointer != NULL);
// compile time assert ( C++ 11 )
static_assert(sizeof(int) == 4);

// Delegating Constrctor
// C++ 03
class dog {
  init() {...};
public:
  dog() {init();}
  dog(int a) {init(); doOtherThings();}
};
// C++ 11
class dog {
public: 
  dog() {...}
  dog(int a) : dog() {...} // dog() has to be called first
};

// virtual override [compile check]

// final (for virtual function and for class)

// Compiler Generated Default Constructor, eg: dog(){}

// delete
dog& operator=(const dog&) = delete

char* a = u8"string"; // define an UTF-8 string 

// lanbda function
cout << [](int x, int y) {return x + y} (3, 4) << endl; // 7

Rvalue Reference

void printInt(int& i) {cout << "lvalue reference: " << i << endl;}
void printInt(int&& i) {cout << "rvalue reference: " << i << endl;}
int main() {
  int a = 5; // lvalue
  int& b = a; // lvalue reference
  int&& c;  // rvalue reference

  printInt(a); // Call printInt(int& i)
  printInt(6); // Call printInt(int&& i)
}
void foo(X x);
void foo_by_ref(X& x);
void main() {
  X x;
  foo_by_ref(x); // Call no constructor
  foo(x); // Call copy constructor
}

二叉树Z型遍历

二叉树

        1
       /  \
      2    3 
     /  \  / \
    4   5 6  7

输出: 1 2 3 7 6 5 4

#include <iostream>
#include <vector>
#include <queue>
#include <list>

using namespace std;
struct tree_node {
    struct tree_node* left;
    struct tree_node* right;
    int data;
    tree_node(int d){
        data = d;
        left = NULL;
        right = NULL;
    }
};

tree_node* build_binary_tree(const vector<int>& nums) {
    if (nums.empty()) return NULL;

    tree_node* root = new tree_node(nums[0]);
    queue<tree_node*> q;
    q.push(root);
    int idx = 1;
    while(!q.empty()) {
        tree_node* cur = q.front();
        q.pop();

        if (idx < nums.size()) {
            tree_node* node = new tree_node(nums[idx++]);
            cur->left = node;
            q.push(node);
        } else break;

        if (idx < nums.size()) {
            tree_node* node = new tree_node(nums[idx++]);
            cur->right = node;
            q.push(node);
        } else break;
    }
    return root;
}

void binary_tree_travel(struct tree_node* node) {
    if (node == NULL) return;
    if (node->left) binary_tree_travel(node->left);
    cout << node->data << " ";
    if (node->right) binary_tree_travel(node->right);
}

void zigzag_travel(tree_node* root) {
    if (root == NULL) return;

    bool reverse = true ;
    list<struct tree_node*> last;
    list<struct tree_node*> cur;
    cur.push_back(root);

    while(!cur.empty()) {
        int size = cur.size();
        vector<int> level_node(size);
        for (int i = 0; i < size; i++) {
            tree_node* node = cur.front();
            cur.pop_front();

            if (node->left) last.push_back(node->left);
            if (node->right) last.push_back(node->right);

            int idx = reverse ? size - 1 - i : i;
            level_node[idx] = node->data;
        }

        swap(cur, last);
        reverse = !reverse;

        for (int d : level_node) {
            cout << d << " ";
        }
    }
}

int main()
{
    vector<int> nums{1,2,3,4,5,6,7,8,9,10};
    struct tree_node* root = build_binary_tree(nums);
    binary_tree_travel(root);
    cout << "\n";

    zigzag_travel(root);
    cout << "\n";
    cout << "Hello World" << endl;
    return 0;
}

/*
8 4 9 2 10 5 1 6 3 7
1 2 3 7 6 5 4 8 9 10
Hello World
 */

C++ 模板最佳实践

模板参数

Template Parameters有三种类型:

  • Type parameters(类型参数);这种参数最常用 template <typename T>
  • Non-type parameters(非类型参数 - 整数或枚举类型 编译期常数、pointer类型、reference类型); template <size_t N = 1> template <typename T, typename T::Allocator* Allocator>
    Non-type parameters总是右值,不能取被取址,也不能被赋值
  • Template template parameters(双重模板参数) template <typename T, template <typename ELEM, typename ALLOC = std::alloc<ELEM>> class CONT = std::vector>

Template引入的“双阶段名称查找(Two phase name lookup)”堪称是C++中最黑暗的角落,名称查找会在模板定义和实例化时各做一次,分别处理非依赖性名称和依赖性名称的查找。

实例化模板匹配的时候,通过原型得到模板实参类型,然后计算偏特化和全特化形式的匹配,如果无法匹配则使用原型

Template template parameters

#include <iostream>
#include <vector>
#include <deque>
#include <typeinfo>
#include <cxxabi.h>

template <typename T,
        template<typename ELEM, typename ALLOC = std::allocator<ELEM>>
        class CONT = std::deque >
class NestTemplate{
public:
    NestTemplate() {
        std::cout << abi::__cxa_demangle(typeid(CONT<T>).name(), nullptr, nullptr, nullptr) << std::endl;
    }
    template <typename N>
    void nest_func(N n) {
        std::cout << n << std::endl;
    }
private:
    CONT<T> elem_;
};

int main() {
    NestTemplate<int> t;
//    t.nest_func(5);

    std::cout << abi::__cxa_demangle(typeid(int).name(), nullptr, nullptr, nullptr) << std::endl;

    return 0;
}

嵌套模板,模板类型作为模板参数
template template argumnets(实参)必须完全匹配其对应参数

avoid array to pointer

模板参数可以防止array转为pointer的转型动作,常被称为退化

template <typename T>
void avoid_decay_func(T& args) { //这里必须是引用传递
    std::cout << typeid(T&).name() << std::endl;
    std::cout << abi::__cxa_demangle(typeid(T&).name(), nullptr, nullptr, nullptr) << std::endl;
    std::cout << sizeof(args) << std::endl;
}
int main() {
    char type_array[10]{'0'};
    avoid_decay_func(type_array);
}
/*
A10_c
char [10]
10
*/

char array 与 char pointer

type has member type X

在编译器判定某给定类型T是否有member type x

template <typename T>
auto contains_of(typename T::X const*) -> char {
    return static_cast<char>(0);
}
template <typename T>
auto contains_of(...) -> char*{
    return static_cast<char*>(0);
}

#define type_has_member_type_X(T) \
    (sizeof(contains_of<T>(0)) == 1)

struct Test{
    typedef decltype(nullptr) X;
};

int main() {
    std::cout << type_has_member_type_X(Test) << std::endl; //1
    return 0;
}

此宏的功用是判别编译器实例化contains_of(0)时选中第一个function template或第二个,编译器把算式值绑定至template parameters时

  • 最佳匹配:重载解析规则会优先考虑把0转换为一个null指针,而不考虑把0值当做省略号(ellipsis)参数,重载解析规则中最后才考虑是否匹配的参数形式
  • SFINAE原则:替换失败并非错误

模板与设计

  • Generic programming(泛型编程):使用泛化参数进行编程,以便找出高效算法之最抽象表述
  • Traits(特征、类型提取):traits来表达与某给定主类型有所关联的类型附加信息,相当于类型绑定特征
  • Policy classes(策略类别)
  • Meta-programming(超编程)
  • Expression templates(算式模板)
     

参考资料

《C++ Template 全览 - 侯捷 简体中文版》

HTTP协议数据的结束

Assuming you want your client to work with other servers, and server to work with other clients, your server can't expect to be treated nicely.
There are two ways to tell when the body has ended. Neither of them require knowledge of the body's content type as you suggest (e.g., don't bother looking for -- that goes far outside the HTTP protocol).
1、If the client sends a message with Transfer-Encoding: Chunked, you will need to parse the somewhat complicated chunked transfer encoding syntax. You don't really have much choice in the matter -- if the client is sending in this format, you have to receive it. When the client is using this approach, you can detect the end of the body by a chunk with a length of 0.
2、If the client instead sends a Content-Length, you must use that.

HTTP由请求头、请求行、请求主体构成,没有Content-Length 只有两种情况,否则就不是一个标准的http server
1、传输完毕就关闭connection
2、HTTP 1.1 没有Content-Length,但是 Transfer-Encoding: chunked,最后一个chunk的length==0

总体流程大致是:
1、先把header直到\r\n\r\n整个地收下来;
2、如果Connection: Keep-Alive:
1)if T-E: chunked, 就读, 直到流里有\r\n0\r\n\r\n

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25
This is the data in the first chunk

1C
and this is the second one

3
con

8
sequence

0

2)else if Content-Length存在, 就从头到末尾开始计算C-L个字节.
3、else 就这么一直读等服务器断开连接就好.

Kaggle入门Titanic

跟着一个kernel一步一步做下来的,算是一个小白入门,感受最深的一点就是特征工程非常重要,对数据的理解程度决定了最终模型结果的上限,训练模型只是去不断逼近这个上限

另外就是对各种训练模型的理解,学会用seaborn画图,形象的理解数据对做特征工程也是一个重要的点

Notebook的kernel上传到了GitHub上

TitanicKernel.ipynb

vultr部署shadowsocks

vultr 部署shadowsocks

Centos

安装shadowsocks

$ yum install m2crypto python-setuptools
$ easy_install pip
$ pip install shadowsocks

vi /etc/shadowsocks.json

{
    "server":"::",
    "server_port": 443,
    "local_address": "127.0.0.1",
    "local_port":1080,
    "password":"12345678",
    "timeout":300,
    "method":"aes-256-cfb",
    "fast_open": true
}

OR

{
    "server":"::",
    "local_address": "127.0.0.1",
    "local_port":1080,
    "port_password":{
        "443":"12345678",
	"1234":"12345678"
    },
    "timeout":300,
    "method":"aes-256-cfb",
    "fast_open": true
}

配置防火墙

安装防火墙

$ yum install firewalld

启动防火墙

$ systemctl start firewalld

端口号是你自己设置的端口

$ firewall-cmd --permanent --zone=public --add-port=443/tcp
$ firewall-cmd --reload

启动

ssserver -c /etc/shadowsocks.json #前台终端启动
ssserver -c /etc/shadowsocks.json -d start #后台启动
ssserver -d stop #停止

开启BBR TCP加速

BBR TCP加速

[root@vultr ~]# uname -r
3.10.0-514.26.2.el7.x86_64

PC client设置

Server Ip: 2001:19f0:7001:30b7:5400:00ff:fe88:6f1d
Server Port: 443
Password: 12345678
Local Port: 1080 (任选)
Encryption: AES-256-CFB

ipv6相关设置

sudo apt-get install isatapd     network-manager

win下ipv6
win10 ipv6

~ cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback
/etc/init.d/networking restart

Advanced C++

const

// reference cannot be reassigned
string& m_name = "Hello";

// A compile time constraint that an object can not be modified 
const int i = 9;
i = 6  //assignment of read-only variable 'i'

const int *p1 = &i;  //data is const, pointer is not 
int* const p2 = &i;  //pointer is const, data is not
const int* const p3; //data and pointer are both const

// If const is on the left of *, data is const
// if const is on the right of *, pointer is const
int const *p4 = &i;
const int *p5 = &i;

// cast is not a good thing
const_cast<int&>(i) = 6;

int j;
static_cast<const int&>(j);

const used with functions

class Dog {
    int age;
    string name;

public:
    Dog() {
        age = 3;
        name = "dummy";
    }

    // const parameters
    void setAge(const int& a) { age = a; }

    // const return value
    const string& getName() {return name;}

    // const function
    // this function will not change any of the member valuables of this class
    // a const function can only call another const function
    void printDogName() const { cout << name << endl; }
    //can be overload
    void printDogName() const { cout << name << endl; }
};

int main() {
  Dog d;

  int i = 9;
  d.setAge(i);

  const string& n = d.getName();
  
  const Dog d2;
  d.printDogName();
  d2.printDogName();
}
class BigArray {
  vector<int> v; // huge vector
  int accessCounter;

  int* v2;  //another int array

public:
  int getItem(int index) const {
    //accessCounter++;
    const_cast<BigArray*>(this)->accessCounter++;
    return v[index];
  }

  // const can be remove
  void setV2Item(int index, int x) const {
    *(v2 + index) = x;
  }
};

Compiler Generated Functions

Compiler silnetly writes 4 functions if they are not explicitly declared

  1. Copy constructor
  2. Copy Assignment Operator
  3. Destructor
  4. Default constructor (only if there is no constructor declared)
class dog {};

/* equivalent to  */
class dog {
  public: 
    dog(const dog& rhs) {...}; // Member by member initialization
    dog& operator=(const dog& rhs) {...}; // Member by member copying
    dog() {...};  //1. Call base class's default constructor;
                     //2. Call data member's default constructor;
    ~dog() {...}; //1. Call base class's destructor;
                       //2. Call data member's destructor;
};

Disallow Functions

class OpenFile{
public:
  OpenFile(string filename) { cout << "Open a file " << filename << endl;}
  OpenFile(OpenFile& rhs) = delete;
  //or
/*
private:
  OpenFile(OpenFile& rhs);  // no function body
**/
};

int main() {
  OpenFile f(string("Bo_file"));
  
  // wrong
  OpenFile f2(f);
}
class OpenFile{
public:
  OpenFile(string filename) { cout << "Open a file " << filename << endl;}
  void destroyMe() { delete this; }

private:
  ~OpenFile() {cout << "OpenFile destructed."  << endl;}
};

int main() {
  // a class like OpenFile which has a private destructor can only be stored on heap,
  // it can not be stored on stack
  OpenFile* f = new OpenFile(string("Bo_file"));
  f->destroyMe();
}

Summary

  1. C++ 11: f() = delete;
  2. C++ 03: Declare the function to be private, and not define it.
  3. Private destructor: can only be stored on heap. stay out of stack.

Virtual Destructor and Smart Destructor

/* Use virtual destructors in polymorphic(多态) base classes */
// base class
class Dog {
public: 
  // this class will be used polymorphically which means most likely it will need a virtual destructor
  virtual void bark() {}
  virtual ~Dog() { cout << "Dog destroyed." << endl; }
};

class Yellowdog : public Dog {
public: 
  ~Yellowdog() { cout << "Yellow dog destroyed." << endl; }
};

class DogFactory {
public:
  static Dog* createYellowDog() { return (new Yellowdog()); }
  // ... create other dogs
};

int main() {
  Dog* pd = DogFactory::createYellowDog();
  //... Do something with pd

  delete pd;
  return 0;
}
class Dog {
public: 
  ~Dog() { cout << "Dog destroyed." << endl; }
};

class Yellowdog : public Dog {
public: 
  ~Yellowdog() { cout << "Yellow dog destroyed." << endl; }
};

class DogFactory {
public:
  static shared_ptr<Dog> createYellowDog() { 
    return shared_ptr<Yellowdog>(new Yellowdog()); 
  }
  // ... create other dogs
};

int main() {
  shared_ptr<Dog> pd = DogFactory::createYellowDog();
  //... Do something with pd

  return 0;
}

Notes:

All classes in STL have no virtual destructor, so be careful inheriting from them.

Exceptions in Destructors

class dog {
public: 
  string m_name;
  dog(string name) { m_name = name; cout << name << " is born." << endl;}
  ~dog() { cout << m_name << " is destroyed." << endl;}
  void bark() {...}
};

int main() {
  try {
    dog dog1("Henry");
    dog dog2("Bob");
    throw 20;
    dog1.brak();
    dog2.bark();
  } catch (int e) {
    cout << e << " is caught." << endl;
  }
}

output: the stack will unwind and all the local variables inside the try block needs to be destroyed.

Henry is born.
Bob is born.
Bob is destroyed.
Henry is destroyed.
20 is caught.

// c++ doesn't like having work more than one exception pending at the same time
// so it will just crash
~dog() {
  try {
    // Enclose all the exception prone code here
  } catch (MYEXCEPTION e) {
    // Catch exception
  } catch (...) {
  }
}

// or 
class dog {
public: 
  string m_name;
  dog(string name) { m_name = name; cout << name << " is born." << endl;}
  ~dog() { cout << m_name << " is destroyed." << endl;}
  void prepareToDestr() {...; throw 20;}
  void bark() {...}
};
int main() {
  try {
    dog dog1("Henry");
    dog dog2("Bob");
    dog1.brak();
    dog2.bark();

    dog1.prepareToDestr();  // goto catch
    dog2.prepareToDestr();
  } catch (int e) {
    cout << e << " is caught." << endl;
  }
}

Virtual Function [dynamic binding]

class Dog {
public: 
  Dog() { cout << "Dog born." << endl; }
  virtual void bark() { cout << "I am just a dog" }
  void seeCat() { bark(); }
};

class Yellowdog : public Dog {
public: 
  Yellowdog() { cout << "Yellow dog born." << endl; }
  virtual void bark() { cout << "I am a yellow dog" << endl; }
};

int main() {
  Yellowdog d;
  d.seeCat();

  return 0;
}

Summary

Should avoid calling virtual function in a constructor or destructor.

Handling Self-Assignment

dog dd;
dd = dd; // Looks silly

dogs[i] = dogs[j]; // self assignment
class collar;
class dog {
  collar* pCollar;
  dog& operator=(const dog& rhs) {
    if (this == &rhs) {
      return *this;
    }

    collar* pOrigCollar = pCollar;
    pCollar = new collar(*rhs.pCollar);
    delete pOrigCollar;
    return *this;
  }
};

//or
class dog {
  collar* pCollar;
  dog& operator=(const dog& rhs) {
    *pCollar = *rhs.pCollar;  // member by member copying of collars or
                                          // call collar's operator=
    return *this;
  }
};

Resource Acquisition Initialization

// not good 
Mutex_t mu = MUTEX_INITIALIXZER;

void functionA()
{
  Mutex_lock( &mu );
  //...
  Mutex_unlock( &mu ); // will this line always be executed?
}
class Lock {
  private:
    Mutex_t* m_pm;
  public: 
    explicit Lock(Mutex_t* pm) { Mutex_lock(pm); m_p = pm; }
    ~Lock() {Mutex_unlock(m_pm);}
};

void functionA()
{
  Lock mylock(&mu);
  // ...
  // The mutex will always be released when mylock is destroyed from stack.
}
int functionA() {
  // The default value for D is "delete"
  shared_ptr<dog> pd(new dog());
  //...
  // The dog is destructed when pd goes out of scope
}
class Lock {
private:
  shared_ptr<Mutex_t> pMutex;
public:
  explicit Lock(Mutex_t *pm) : pMutex(pm, Mutex_unlock) {
    Mutex_lock(pm);
  // The second parameter of shared_ptr constructor is "delete" function.
  }
};

Lock L1(&mu);
Lock L2(L1);

Strcut Vs Class

// Data container
struct Person_t {
  string name;  // public
  unsigned age;
};

// Complex Data Structure
class Person {
  string name_;  //private
  unsigned age_;
};

Notes: avoid using too many of setter and getter function

Resource Managing Class

class Person {
public:
    Person(string name) { pName_ = new string(name); }
    ~Person() { delete(pName_); }
    void printName() { cout << *pName_ << endl;}

    // 构造拷贝
    Person(const Person& rhs) {
        // make a deep copy
        pName_ = new string(*(rhs.pName()));
    }
    // 赋值拷贝
    Person& operator=(const Person& rhs) {
        *pName_ = *rhs.pName();
        return *this;
    }

    string* pName() const { return pName_; }

private:
    string* pName_;
};
TEST(leetcode, advanced_test_resource_manage_class) {
    vector<Person> persons;
    persons.push_back(Person("Bob"));
    // 1. "Bob" is constructed
    // 2. A copy of "Bob" is saved in the vector persons (shallow copy)
    // 3. the copy of "Bob" is destroyed
    persons.back().printName();

    Person b = Person("nick");
    b.printName();

    cout << "Bye" << endl;
}
class Person {
public:
    Person(string name) { pName_ = new string(name); }
    ~Person() { delete(pName_); }
    void printName() { cout << *pName_ << endl;}

    string* pName() const { return pName_; }
    Person* clone() {
        return (new Person(*pName_));
    }

private:
    string* pName_;
    Person(const Person& rhs);
    Person& operator=(const Person& rhs);
};
TEST(leetcode, advanced_test_resource_manage_class) {
    vector<Person*> persons;
    persons.push_back(new Person("Bob"));

    persons.back()->printName();


    cout << "Bye" << endl;
}

Summary : one object owning another object through its pointer

Virtual Constructor - Clone() Function

class Dog {
public:
  virtual Dog* clone() { return (new Dog(*this)); }
};

class YellowDog : public Dog {
  virtual YellowDog* clone() { return (new YellowDog(*this)); }
};

void foo(Dog* d) {  //d is a Yellowdog
  // Dog* c = new Dog(*d); // c is a Dog
  Dog* c = d->clone();  //c is a YellowDog
}

int main() {
  YellowDog d;
  foo(&d);
}

Type Conversion

Implicit 隐含的、Explicit 明确的 [casting]

// implicit
char c = 'A';
int i = c;  // Integral promotion

char* pc = 0; // int -> Null pointer

void f(int i);
f(c);

dog* pd = new YellowDog(); // pointer type conversion
operator string() const { return name_; }
/* static_cast */
int i = 9;
// convert object from one type to another
float f = static_cast<float>(i);

// Type conversion needs to be defined
dog d1 = static_cast<dog>(string("Bob"));

// convert pointer/reference from one type to a related type (down/up) cast
dog* pd = static_cast<dog*>(new yellowdog());
/* dynamic_cast */
// 1. It convert pointer/reference from one type to a related type (down cast)
// 2. run-time type check. If succeed, py == pd; if fail, py == 0;
// 3. It requires the 2 types to be polymorphic (have virtual function)

/* all yellowdogs are dogs, but not all dogs are yellowdog */
dog* pd = new yellowdog();
yellowdog* py = dynamic_cast<yellowdog*>(pd);
/* const_cast */
// Only works on pointer/reference
// Only works on same type, cast away constness of the object being pointed to

const char* str = "Hello, world.";
char* modifiable = const_cast<char*>(str);
/* reinterpret_cast */
// The ultimate cast that can cast one pointer to any other type of pointer

long p = 51110980;
dog* pd = reinterpret_cast<dog*>(p);
/* C-Style Casting */
short a = 200;
int j = (int)a;
int j = int(a);

Inheritance - Public, Protected, and Private

/* They specifies different access control from the derived class to the base class */

class B {};

class D_pub : public B {};  // ''is-a'' relation
class D_priv : private B {};  // has-a
class D_prot : protected B {};

/*
 Access Control:
 1. None of the derived classes can access anything that is private in B.
 2. D_pub inherits public members of B as public and the protected members of B as protected.
 3. D_priv inherits public and protected members of B as private.
 4. D_prot inherits public and protected members of B as protected.

 Casting:
 1. Anyone can cast a D_pub* to B*. D_pub is a special kind of B.
 2. D_priv's members and friends can cast a D_priv* to B*.
 3. D_prot's members, fiends, and childrens can cast a D_prot* to B*.
 */

public:
  using dog::bark;

/*
 1. Precise definition of classes.
 2. Don't override non-virtual functions.
 3. Don't override default parameter values for virtual functions.
 4. Force inheritance of shadowed functions.
 */

Understanding rvalue and lvalue

/*
 Simplified Definition:
 lvalue - An object that occupies some identifiable location in memory
 "内存中,有特定位置(地址)的东西"或"指向一个确定对象的东西"
 
 rvalue - Any object that is not a lvalue
 */

Dynamic Polymorphism

struct TreeNode { TreeNode *left, *right; };
class Generic_Parser {
public: 
  void parse_preorder (TreeNode* node) {
    if (node) {
      process_node(node);
      parse_preorder(node->left);
      parse_preorder(node->right);
    }
  }

private:
  virtual void process_node(TreeNode* node){}
};

class EmployeeChart_Parser : public Generic_Parsesr {
private:
  void process_node(TreeNode* node) {
    cout << "Customized process_node() for EmployeeChart." << endl;
  }
};

int main() {
  ...
  EmployeeChart_Parser ep;
  ep.parse_preorder(root);
}

Summary:

  1. the memory cost of the virtual table
  2. the runtime cost of dynamic binding

Static Polymorphism

template<typename T> class Generic_Parser {
public: 
  void parse_preorder(TreeNode* node) {...}
  void process_node(TreeNode* node) {
    static_cast<T*>(this)->process_node(node);
  }
};

class EmployeeChart_Parser : public Generic_Parser<EmployeeChart_Parser> {
public:
  void process_node(TreeNode* node) {
    cout << "Customized process_node() for EmployeeChart." << endl;
  }
};

int main() {
  ...
  EmployeeChart_Parser ep;
  ep.parse_preorder(root);
  ...
  // 以空间换时间
  MilitaryChart_Parser mp;
}
template<typename T>
T Max(vector<T> v) {
  T max = v[0];
  for (typename std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    if (*it > max) {
      max = *it;
    }
  }
  return max;
}

Multiple Inheritance

// 菱形继承
class File {                 //                 File
public:                        //               /        \
  string name;             //  InputFile        OutputFile
  void open();              //               \        /
};                                 //               IOFile

class InputFile : public File {
public: 
  void read();
};

class OutputFile : public File {
public: 
  void write();
};

class IOFile : public InputFile, public OutputFile {};

int main() {
  IOFile f;
  f.InputFile::name = "file1";
  f.OutputFile::name = "file2";
}
class File {               
public:                      
  string name;          
  void open();             
};                                 
// only needs one instance of File
class InputFile : virtual public File {
public: 
  void read();
};

class OutputFile : virtual public File {
public: 
  void write();
};

class IOFile : public InputFile, public OutputFile {};

int main() {
  IOFile f;
  f.open();
}
class File {               
public:                      
  string name;          
  File(string fname);          
};                                 

class InputFile : virtual public File {
public: 
  InputFile(string fname) : File(fname) {}
};

class OutputFile : virtual public File {
public: 
  OutputFile(string fname) : File(fname) {}
};

class IOFile : public InputFile, public OutputFile {
  IOFile(string fname) : InputFile(fname), OutputFile(fname), File(fname) {}
};

int main() {
  IOFile f;
}
/*
 Abstract class : A class has one or more pure virtual functions
 Pure abstract class : A class containing only pure virtual functions
   - no data
   - no concrete functions 
 */
class OutputFIle {
public:
  void write() = 0;
  void open() = 0;
};

Namespace and Keyword "using"

/*
 * C++ Keyword: using
 * 1. using directive : to bring all namespace members into current scope 
 */
using namespace std;

/*
 * 2. using declaration
 *   a. Bring one specific namespace member to current scope
 *   b. Bring a member from base class to current class's scope
 */
using std::cout;
cout << "Hello world.\n";

class B {
public: 
  void f(int a);
};
class D : private B {
public: 
  using B::f;  // only using in class scope
};
int main() {
  D d;
  d.f(8);
}
/*
 * Anonymous Namespace
 */
// accessed with the same file
namespace {
  void h() {std::cout << "h()\n";}
}
// static viod h() {std::cout << "h()\n";}
int main() {
  h();
}

C++并发编程实战_笔记

C++ Concurrency in Action - Practical Multithreading

C++的并发世界

多进程并发,消息传递机制(信号、套接字、文件、管道等等)
多线程并发,进程中的所有线程都共享地址空间, 并且所有线程访问到大部分数据——全局变量仍然是全局的,指针、对象的引用或数据可以在线程之间传递

对线程的划分是基于概念上的设计,而不是一种增加吞入量的尝试
每个线程都需要一个独立的堆栈空间,运行太多的线程也会耗尽进程的可用空间

线程管理

启动线程

std::thread my_thread(background_task());
这里相当于声明了一个名为my_thread的函数,这个函数带有一个参数(函数指针指向没有参数并返回background_task对象的函数),返回一个std::thread对象的函数,而非启动了一个线程

// 使用多组括号
std::thread my_thread((background_task()));

// 或使用新统一的初始化语法
std::thread my_thread{background_task()};

// 使用lambda表达式,允许使用一个可以捕获局部变量的局部函数
std::thread my_thread([]{
  do_something();
});

启动了线程,需要明确是要等待线程结束(join),还是让其自主运行(detach)

悬空引用

如果不等待线程,就必须保证线程结束之前,可访问的数据的有效性
这种情况和可能发生在线程还没结束,函数已经退出的时候,这时线程函数还持有函数局部变量的指针或引用

struct func {
    int& i_;
    func(int& i) : i_(i){}
    void operator()() {
        using namespace std;
        for (unsigned int i = 0; i < 65535; ++i) {
            cout << i_ << endl;     // 潜在访问隐患:悬空引用
        }
    }
};

// oops崩溃
void oops() {
    int local_state = 0;
    func my_func(local_state);

    std::thread t1(my_func);
    t1.detach();
    /* 不等待线程结束,新线程可能还在运行
      * */
}

特殊情况下的等待

struct func {
    int& i_;
    func(int& i) : i_(i){}
    void operator()() {
        do_something(i_);
    }
};

void do() {
    int local_state = 0;
    func my_func(local_state);

    std::thread t1(my_func);
    try {
      do_something_in_current_thread();
    } catch(...) {
      t1.join(); // 异常流
      throw;
    }

    t1.join(); // 正常流
}

使用了try/catch块确保访问本地状态的线程退出后,线程才结束

还有一种方式是使用资源获取即初始化方式,并且提供一个类,在析构函数中使用join()

struct func;
class thread_guard{
    std::thread& t_;
public:
    explicit thread_guard(std::thread& t) : t_(t) {}
    ~thread_guard() {
        if (t_.joinable())
            t_.join();
    }

    thread_guard(const thread_guard&) = delete;
    thread_guard& operator=(const thread_guard&) = delete;
};

void do() {
    int local_state = 0;
    func my_func(local_state);

    std::thread t1(my_func);
    thread_guard tg(t1);

    do_something_in_current_thread();
}

join()只能对给定的对象调用一次,所以对给已加入的线程再次进行加入操作时,将会导致错误

如果不想等待线程结束,可以分离(detach)线程,从而避免异常安全问题。不过,这就打破了线程与std::thread对象的联系,即使线程仍然在后台运行着,分离操作也能确保std::terminate()在std::thread对象销毁才被调用

后台线程

使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有std::thread对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。不过C++运行库保证,线程退出时,相关资源能够正确回收,后台线程的归属和控制C++运行库都会处理。通常称分离线程为守护线程

向线程函数传递参数

需要注意的是,默认参数要拷贝到线程独立内存中,即使参数是引用的形式,也可以在新线程中进行访问

void f(int i, std::string const& s);  
void oops(int some_param) {
  char buffer[1024];
  sprintf(buffer, "%i", some_param);
  std::thread t(f, 3, buffer);
  t.detach();
}

这种情况下,buffer是一个指针变量,指向本地变量,然后本地变量通过buffer传递到新线程中。并且函数有很大可能,会在字面值转化成std::string对象之前崩溃,从而捯饬线程的一些未定义行为。解决方案是在传递到std::thread构造函数之前就将字面值转化为std::string对象
std::thread t(f, 3, std::string(buffer));

std::bind
bind的**实际上是一种延迟计算的**,将可调用对象保存起来,然后在需要的时候再调用

void add(int a, int b, int& r) {
    r = a + b;
}
int main() {
    int ret = 0;
    // 函数柯里化,_1相当于占位符
    auto f = std::bind(add, std::placeholders::_1, 20, std::ref(ret));
    f(10);
    std::cout << ret << std::endl; // 30
    return 0;
}

std::ref返回的是std::reference_wrapper,是一个引用封装类
假如std::bind(add, std::placeholders::_1, 20, ret)传递给函数的参数是ret变量内部拷贝的引用,而非数据本身的引用

class X{
  public:
    void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work(), &my_x);

可以传递一个成员函数指针作为线程函数,并提供一个合适的对象指针作为第一个参数

提供的参数可以移动(move),但不能拷贝(copy)。移动是指:原始对象中的数据转移给另一对象,而转移的这些数据就不再在原始对象中保存了。使用移动转移原对象后,就会留下一个空指针(NULL)
展示了std::move()是如何转移一个动态对象到一个线程中去的

void process_big_object(std::unique_ptr<big_object>);

std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object, std::move(p));

在std::thread的构造函数中指定std::move(p),big_object对象的所有权就被首先转移到新创建线程的内部存储中,之后传递给process_big_object函数

转移线程所有权

在同一时间点,就能保证只关联一个执行线程
当原对象是一个临时变量时,自动进行移动操作

void some_function();
void some_other_function();

std::thread t1(some_function);
std::thread t2 = std::move(t1);

t1 = std::thread(some_other_function); // 右值,即将消亡  临时对象

std::thread t3;
t3 = std::move(t2);

t1 = std::move(t3); //赋值操作将使程序崩溃

t1已经有了一个关联的线程(执行some_other_function的线程),所以这里直接调用std::terminate()终止程序继续执行。终止操作将调用std::thread的析构函数,销毁所有对象(与C++中异常的处理方式很相似)

当所有权可以在函数内部传递,就允许std::thread实例可以作为参数进行传递

void f(std::thread t);
void g() {
    void some_function();
    f(std::thread(some_function));

    std::thread t(some_function);
    f(std::move(t));
}

std::thread支持移动的好处是可以创建thread_guard类的实例,并且拥有其线程的所有权
为了确保线程程序在退出前完成,可以定义scoped_thread类

class scoped_thread{
    std::thread t_;
    public:
        explicit scoped_thread(std::thread t) : t_(std::move(t)) {
            if(!t_.joinable) 
                throw std::logic_error("No thread");
        }

        ~scoped_thread() {
            t_.join();
        }

        scoped_thread(scoped_thread const&) = delete;
        scoped_thread& operator=(scoped_thread const&) = delete;
};

struct func;
void f() {
    int some_local_state;
    // 新线程直接传递到scoped_thread中,而非创建一个独立的命名变量
    scoped_thread t(std::thread(func(some_local_state))); 
    do_something_in_current_thread();
}

量产线程

void do_work(unsigned id);
void f() {
    std::vector<std::thread> threads;
    for (unsigned i = 0; i < 20; i++) {
        threads.push_back(std::thread(do_work, i));
    }

// 对每个线程调用join
    std::for_each(threads.begin(), threads.end(), 
        std::mem_fn(&std::thread::join));
}

运行时决定线程数量

std::thread::hardware_concurrency()将返回能同时并发在一个程序中的线程数量
多核系统中,返回值可以是CPU核心的数量。返回值也仅仅是一个提示,当系统信息无法获取时,函数也会返回0

并行版的std::accumulate

template<typename Iterator, typename T> 
struct accumulate_block {
    void operator()(Iterator first, Iterator last, T& result) {
        result = std::accumulate(first, last, result);
    }
}

template<typename Iterator, typename T> 
T parallel_accumulate(Iterator first, Iterator last, T init) {
    unsigned long const length = std::distance(first, last);

    if (!length) return init;

    unsigned long const min_per_thread = 25;
    unsigned long const max_threads = (length + min_per_thread - 1) /
        min_per_thread;  // [1]

    unsigned long const hardware_threads = std::thread::hardware_concurrency();

    unsigned long const num_threads = std::min(hardware_threads != 0 ?
        hardware_threads : 2, 
        max_threads);  // [2]

    unsigned long const block_size = length/num_threads;

    std::vector<T> results(num_threads);
    std::vector<std::thread> threads(num_threads - 1); // [3]

    Iterator block_start = first;
    for (unsigned long i = 0; i < (num_threads - 1); ++i) {
        Iterator block_end = block_start;
        std::advance(block_end, block_size);
        threads[i] = std::thread(accumulate_block<Iterator, T>(),
            block_start, block_end, std::ref(results[i]));
        block_start = block_end;
    }

    accumulate_block<Iterator, T>()(
        block_start, last, results[num_threads - 1]
    );  // [4]

    std::for_each(threads.begin(), threads.end(), 
        std::mem_fn(&std::thread::join));

    return std::accumulate(results.begin(), results.end(), init);
}

[1] 如果范围内多于一个元素,都需要使用范围内的总数量除以线程块中最小任务数,从而确定启动线程的最大数量,这样能避免无谓的计算资源的浪费。比如一台32芯的机器上,只有5个数需要计算,却启动了32个线程

[2] 计算量的最大值和硬件支持线程数中,较小的值为启动线程的数量。因为上下文频繁的切换,所以肯定不想启动的线程数多于硬件支持的线程数量。当std::thread:hardware_concurrency()返回0,可以选择一个合适的数作为选择,这里选择的是2

[3] 启动的线程数必须比num_threads少一个,因为在启动之前已经有了一个主线程

[4] 启动所有线程后,主线程会处理最终块的结果。对于分配不均,因为知道最终块是哪一个,那么这个块中有多少元素就无所谓了

因为不能直接从一个线程中返回一个值,所以需要传递results容器的引用到线程中去。另一个办法,通过地址来获取线程执行的结果

识别线程

线程标识类型是std::thread::id,可以通过两种方式进行检索。第一种,可以通过调用std::thread对象的成员函数get_id()来直接获取。如果std::thread对象没有与任何执行线程相关联,get_id()将返回std::thread::type默认构造值,这个值表示没有线程。第二种,当前线程调用std::this_thread::get_id()(这个函数定义在头文件中)也可以获得线程标识

允许程序员将线程id作为容器的键值,做排序。标准库也提供std::hashstd::thread::id容器,所以std::thread::id也可以作为无序容器的键值

std::thread::id实例常用作检测线程是否需要进行一些操作。比如:用线程来分割一项工作,主线程可能要做一些与其他线程不同的工作

std::thread::id master_thread;
void some_core_part_of_algorithm() {
    if (std::this_thread::get_id() == master_thread) {
        do_master_thread_work();
    }

    do_common_work();
}

线程间共享数据

共享数据带来的问题

线程间潜在问题就是修改共享数据,致使不变量遭到破坏

条件竞争

C++标准中也定义了数据竞争这个术语,一种特殊的条件竞争:并发的去修改一个独立对象,数据竞争是(可怕的)未定义(undefine behavior)的起因

避免恶性条件竞争

最简单的办法就是对数据结构采用某种保护机制,确保只有进行修改的线程才能看到不变量被破坏时的中间状态。从其他访问线程的角度来说,修改不是已经完成了,就是还没开始。
另一个选择是对数据结构和不变量的设计进行修改,修改完的结构必须能完成一系列不可分割的变化,也就是保证每个不变量保持稳定的状态,这就是所谓的无锁编程(lock-free-programming)。不过,这种方式很难得到正确的结果,如果到这个级别,无论是内存模型上的细微差异,还是线程访问数据的能力,都会让工作变的复杂
另一种处理条件竞争的方式是,使用事务(transacting)的方式去处理数据结构的更新(如同数据库进行更新一样)。所需的一些数据和读取都存储在事务日志中,然后将之前的操作合为一步,再进行提交。当数据结构被另一个线程修改后,或处理已经重启的情况下,提交就会无法进行,这称为软件事务内存

使用互斥量保护共享数据

C++中通过实例化std::mutex创建互斥量,通过调用成员函数lock()进行上锁,unlock()进行解锁。
C++标准库为互斥量提供了一个RAII语法的模板类std::lock_guard,其会在构造的时候提供已锁的互斥量,并在析构的时候进行解锁,从而保证了一个已锁的互斥量总是会被正确的解锁

#include <list>
#include <mutex>
#include <algorithm>

std::list<int> some_list;
std::mutex some_mutex;

void add_to_list(int new_value) {
    std::lock_guard<std::mutex> guard(some_mutex);
    some_list.push_back(new_value);
}

bool list_contains(int value_to_find) {
    std::lock_guard<std::mutex> guard(some_mutex);
    return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end();
}

全局变量被一个全局互斥量保护,使得这两个函数中对数据的访问是互斥的:list_contains()不可能看到正在被add_to_list()修改的列表
在大多数情况下,互斥量通常会与保护的数据放在同一个类中,互斥量和要保护的数据,在类中都需要定义为private成员。当所有的成员函数都会在调用时对数据上锁,结束时对数据解锁,那么就保证了数据访问时不变量不被破坏。也有例外情况,当其中一个成员函数返回的是保护数据的指针或引用时,会破坏对数据的保护

精心组织代码来保护共享数据

使用互斥量保护数据,并不是仅仅在每一个成员函数中都加入一个std::lock_guard对象那么简单;一个迷失的指针或引用,将会让这种保护形同虚设

无意中传递了保护数据的引用

class some_data {
    int a;
    std::string b;
public:
    void do_something();
};

class data_wrapper {
private:
    some_data data;
    std::mutex m;
public:
    template<typename Function>
    void process_data(Function func) {
        std::lock_guard<std::mutex> l(m);
        func(data); // 1 传递 保护 数据给用户函数
    }
};

some_data* unprotected;
void malicious_function(some_data& protected_data) {
    unprotected = &protected_data;
}

data_wrapper x;
void foo() {
    x.process_data(malicious_function); // 2 传递一个恶意函数
    unprotected->do_something(); // 3 在无保护的情况下访问保护数据
}

切勿将受保护数据的指针或引用传递到互斥锁作用于之外,无论是函数返回值,还是存储在外部可见内存,亦或是以参数的形式传递到用户提供的函数中去

发现接口内在的条件竞争

stack<int> s;
if(!s.empty()) {
    int const value = s.top();
    s.pop();
    do_something(value);
}

以上是单线程安全代码:对一个空栈使用top()是未定义行为。对于共享的栈对象,这样的调用顺序就不再安全了,因为在调用empty()和调用top()之间,可能有来自另一个线程的pop()调用并删除了最后一个元素。使用互斥量对栈内部数据进行保护,但依旧不能阻止条件竞争的发生

线程安全堆栈类

#include <exception>
#include <memory>
#include <mutex>
#include <stack>
struct empty_stack: std::exception
{
    const char* what() const throw() {
        return "empty stack";
    }
};

template<typename T> 
class threadsafe_stack {
private:
    std::stack<T> data;
    mutable std::mutex m;
public:
    threadsafe_stack() : data(std::stack<int>()) {}
    threadsafe_stack(const threadsafe_stack& other){
        std::lock_guard<std::mutex> lock(other.mutex);
        data = other.data; // 在构造函数中执行拷贝
    } 
    threadsafe_stack& operator=(const threadsafe_stack&) = delete;

    void push(T new_value) {
        std::lock_guard<std::mutex> lock(m);
        data.push(new_value);
    }
    std::shared_ptr<T> pop() {
        std::lock_guard<std::mutex> lock(m);
        if (data.empty()) throw empty_stack(); // 检查栈非空

// 在修改堆栈前,分配出缓存值
        std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
        data.pop();
        return res;
    }
    void pop(T& value) {
        std::lock_guard<std::mutex> lock(m);
        if (data.empty) throw empty_stack();

        value = data.top();
        data.pop();
    }
    bool empty() const {
        std::lock_guard<std::mutex> lock(m);
        return data.empty();
    }
};

死锁

它的最大问题就是由两个或两个以上的互斥量来锁定一个操作
避免死锁的一般建议,就是让两个互斥量总以相同的顺序上锁:总在互斥量B之前锁住互斥量A
std::lock,可以一次性锁住多个互斥量,并且没有副作用(死锁风险)

交换操作

class some_big_object;

void swap(some_big_object& lhs, some_big_object& rhs);
class X{
private:
    some_big_object some_detail;
    std::mutex m;
public:
    X(some_big_object const& sd) : some_detail(sd) {}

    friend void swap(X& lhs, X& rhs) {
        if (&lhs == &rhs) return;

        std::lock(lhs.m, rhs.m);
        std::lock_guard<std::mutex> lock_a(lhs.mutex, std::adopt_lock);
        std::lock_guard<std::mutex> lock_b(rhs.mutex, std::adopt_lock);

        swap(lhs.some_detail, rhs.some_detail);
    }
};

std::adopt_lock参数除了表示std::lock_guard对象已经上锁外,还表示现成的锁,而非尝试创建新的锁
当std::lock成功的获取一个互斥量上的锁,并且当其尝试从另一个互斥量上再获取锁时,就会有异常抛出,第一个锁也会随着异常的产生而自动释放,所以std::lock要么将两个锁都锁住,要不一个都不锁

避免死锁的进阶指导

无锁的情况下,仅需要每个std::thread对象调用join(),两个线程就能产生死锁。在这种情况下,没有线程可以继续运行,因为他们正在互相等待

std::unique_lock——灵活的锁

std::unique_lock对象的体积通常要比std::lock_guard对象大,但更加灵活
比如需要锁的所有权从一个域转到另一个域

锁的粒度

void get_and_process_data() {
    std::unique_lock<std::mutex> my_lock(the_mutex);
    some_class data_to_process = get_next_data_chunk();
    mylock.unlock(); // 不要让锁的互斥量越过process()函数的调用
    result_type result = process(data_to_process);
    my_lock.lock(); // 为了写入数据,对互斥量再次上锁
    write_result(data_to_process, result);
}

保护共享数据的初始化过程

延迟初始化——单线程

std::shared_ptr<some_resource> resource_ptr;
void foo() {
    if(!resource_ptr) {
        // 转为多线程时是需要保护的
        resource_ptr.reset(new some_resource);
    }
    resource_ptr->do_something();
}

使用一个互斥量延迟初始化

std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo() {
    std::unique_lock<std::mutex> lk(resource_mutex);
    if(!resource_ptr) {
        // 转为多线程时是需要保护的
        resource_ptr.reset(new some_resource);
    }
    lk.unlock();
    resource_ptr->do_something();
}

C++标准库提供了std::once_flag和std::call_once来处理这种情况

std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag;
void init_resource() {
    resource_ptr.reset(new some_resource);
}
void foo() {
    // 可以完整的进行一次初始化
    std::call_once(resource_flag, init_resource);
    resource_ptr->do_something();
}

在只需要一个全局实例的情况下,这里提供一个std::call_once的替代方案

class my_class;
my_class& get_my_class_instance() {
    static my_class instance; // 线程安全的初始化过程
    return instance;
}

保护很少更新的数据结构

使用一个std::mutex来保护数据结构,在没有发生修改时,它将消减并发读取数据的可能性;这里需要另一种不同的互斥量:读者-写者锁

新的C++标准库应该不提供这样的互斥量,可以使用Boost提供的实现 boost::shared_mutex

简单的DNS缓存示例

#include <map>
#include <string>
#include <mutex>
#include <boost/thread/shared_mutex.hpp>
class dns_entry;

class dns_cache {
    std::map<std::string, dns_entry> entries;
    mutable boost::shared_mutex entry_mutex;
public:
    dns_entry find_entry(std::string const& domain) const {
        // 保护共享和只读权限
        boost::shared_lock<boost::shared_mutex> lk(entry_mutex);
        std::map<std::string, dns_entry>::const_interator const it =
            entries.find(domain);
        return (it == entries.end()) ? dns_entry() : it->second;
    }

    void update_or_add_entry(std::string const& domain, 
        dns_entry const& dns_details) {
        // 提供独占访问权限
        boost::lock_guard<boost::shared_mutex> lk(entry_mutex);
        entries[domain] = dns_details;
    }
};

同步并发操作

等待一个事件或其他条件

第一种方法是,可以持续的检查共享数据标志(用于做保护工作的互斥量),直到另一线程完成工作时对这个标志进行重设;线程消耗宝贵的执行时间持续的检查对应标志,并且当互斥量被等待线程上锁后,其他线程就没有办法获取锁,这样线程就会持续等待

第二个选择是在等待线程检查间隙,使用std::this_thread::sleep_for()进行周期性的间歇

bool flag;
std::mutex m;
void wait_for_flag() {
    std::unique_lock<std::mutex> lk(m);
    while(!flag) {
        lk.unlock();
        // 休眠100ms
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        lk.lock();
    }
}

第三个选择是,使用C++标准库提供的工具去等待事件的发生。通过另一线程触发等待事件的机制是最基本的唤醒方式,这种机制就称为条件变量(condition variable)

等待条件达成

C++标准库对条件变量有两套实现:std::condition_variable和std::condition_variable_any。这两个实现都包含在<condition_variable>头文件的声明中。两者都需要与一个互斥量一起才能工作(互斥量是为了同步);前者仅限于与std::mutex一起工作,而后者可以和任何满足最低标准的互斥量一起工作,从而加上了_any后缀

使用队列在多个线程中转移数据是很常见的

std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;

void data_preparation_thread() {
    while (more_data_to_prepare()) {
        data_chunk const data = prepare_data();
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(data);
        data_cond.notify_one();
    }
}

void data_processing_thread() {
    while(true) {
        // 不是使用lock_guard,后面要解锁互斥量
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, []{return !data_queue.empty();}); // [1]

        data_chunk data = data_queue.front();
        data_queue.pop();
        lk.unlock();

        process(data);
        if(is_last_chunk(data)) break;
    }
}

[1] wait()函数,传递一个锁和一个lambda表达式,作为等待的条件
wait会去检查这些条件,当条件满足时返回。如果条件不满足,wait()函数将解锁互斥量,并且将这个线程置于阻塞或者等待状态。当准备数据的线程调用notify_one()通知条件变量时,处理数据的线程从睡眠状态中苏醒,重新获取互斥锁,并且对条件再次检查,在条件满足的情况下,从wait()返回并继续持有锁。当条件不满足时,线程将对互斥量解锁,并且重新开始等待

使用条件变量构建线程安全队列

#include <queue>
#include <memory> // 为了使用shared_ptr
#include <mutex>
#include <condition_variable>

template<typename T>
class threadsafe_queue{
private:
    mutable std::mutex mut; // 互斥量必须是可变的,empty()
    std::queue<T> data_queue;
    std::condition_variable data_cond;

public:
    threadsafe_queue(){}
    threadsafe_queue(threadsafe_queue const& other) {
        std::lock_guard<std::mutex> lk(other.mut);
        data_queue = other.data_queue;
    }

    void push(T new_value) {
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(new_value);
        data_cond.notify_one();
    }

    void wait_and_pop(T& value) {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data.empty();});
        value = data_queue.front();
        data_queue.pop();
    }

    std::shared_ptr<T> wait_and_pop() {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data.empty();});
        std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
        data_queue.pop();
        return res;
    }

    bool try_pop(T& value) {
        std::lock_guard<std::mutex> lk(mut);
        if(data_queue.empty()) return false;

        value = data_queue.front();
        data_queue.pop();
        return true;
    }

    std::shared_ptr<T> try_pop() {
        std::lock_guard<std::mutex> lk(mut);
        if(data_queue.empty()) return std::shared_ptr<T>();

        std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
        data_queue.pop();
        return res;
    }

    bool empty() const {
        std::lock_guard<std::mutex> lk(mut);
        return data_queue.empty();
    }
};

使用期望等待一次性事件

在C++标准库中,有两种期望,使用两种类型模板实现:唯一期望(std::future<>)和共享期望(std::shared_future<>)
在与数据无关的地方,可以使用std::future与std::shared_future特化模板
虽然希望用于线程间的通讯,但是期望对象本身并不提供同步访问,当多个线程需要访问一个独立期望对象时,必须使用互斥量或类似同步机制对访问进行保护

带返回值的后台任务

与std::thread对象等待运行方式的不同,std::async会返回一个std::future对象,这个对象持有最终计算出来的结果。当需要这个值时,只需要调用这个对象的get()成员函数;并且直到期望状态为就绪的情况下,线程才会阻塞

#include <future>
#include <iostream>
int find_the_answer_to_ltuae();
void do_other_stuff();
int main() {
    std::future<int> the_answer = std::async(find_the_answer_to_ltuae);
    do_other_stuff();
    std::cout << "The answer is " << the_answer.get() << std::endl;
}

任务与期望

std::packaged_task<>对一个函数或可调用对象绑定一个期望
future最主要的目的还是提供一个简单的获取返回值的方法:get()。promise的主要目的是提供一个"put"(或"set")操作,以和future的get()对应。如何匹配future/promise对
packaged_task提供了启动任务线程的简单方法。特别是它处理好了future和promise的关联关系,同时提供了包装代码以保证返回值/异常可以放到promise中

std::packaged_task<>的模板参数是一个函数签名,比如void()就是一个没有参数也没有返回值的函数
指定函数签名的返回类型可以用来标识,从get_future()返回的std::future<>的类型

线程间传递任务,使用std::packaged_task执行一个图形界面线程

#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>

std::mutex m;
// 只是完成任务指标
std::deque<std::packaged_task<void()>> tasks;

bool gui_shutdown_message_received();
void get_and_process_gui_message();

void gui_thread() {  // [1]
    while (!gui_shutdown_message_received()) {  // [2]
        get_and_process_gui_message();  // [3]
        std::packaged_task<void()> task;
        {
            std::lock_guard<std::mutex> lk(m);
            if (tasks.empty()) continue;  // [4]

            task = std::move(tasks.front());  // [5]
            tasks.pop_front();
        } 

        task();  // [6]
    }
}

std::thread gui_bg_thread(gui_thread);

template<typename Func>
std::future<void> post_task_for_gui_thread(Func f) {
    std::packaged_task<void()> task(f);
    stdA::future<void> res = task.get_future();
    std::lock_guard<std::mutex> lk(m);
    tasks.push_back(std::move(task));
    return res;
}

图形界面线程[1]循环直到收到一条关闭图形界面的信息后关闭[2],进行轮询界面消息处理[3],例如用户点击,和执行在队列中的任务。当队列中没有任务[4],它将再次循环;除非,能在队列中提取出一个任务[5],然后释放队列上的锁,并且执行任务[6]。这里,期望与任务相关,当任务执行完成时,其状态会被置为就绪状态

期望存储异常

double square_root(double x) f{
    if (x < 0) {
        throw std::out_of_range("x < 0");
    }

    return sqrt(x);
}

当传递-1到square_root()中时,它将抛出一个异常

std::future<double> f = std::async(square_root, -1);
double y = f.get();

如果改为异步调用,那么这个异常就会存储到期望的结果数据中,之后期望的状态被设置为就绪,之后调用get()会抛出这个存储的异常

当希望存入的是一个异常而非一个数值时,就需要调用set_exception()成员函数,而非set_value()

extern std::promise<double> some_promise;
try {
    some_promise.set_value(calculate_value());
} catch (...) {
    some_promise.set_exception(std::current_exception());
}

多个线程的等待

std::future是只移动的,所以其所有权在不同的实例中互相传递,但是只有一个实例可以获得特定的同步结果;而std::shared_future实例是可拷贝的,所以多个对象可以引用同一关联期望的结果

限定等待时间

介绍两种可能希望指定的超时方式:一种是时延的超时方式,另一种是绝对超时方式。第一种方式,需要指定一段时间(例如,30ms);第二种方式,就是指定一个时间点(如UTC时间)。
多数等待函数提供变量,对两种超时方式进行处理。处理持续时间的变量以_for作为后缀,处理绝对时间的变量以_until作为后缀

std::condition_variable的两个成员函数wait_for()和wair_until()

时钟

时钟当前时间 std::chrono::system_clock::now()

等待一个条件变量,有超时功能

#include <condition_variable>
#include <mutex>
#include <chrono>

std::condition_variable cv;
bool done;
std::mutex m;

bool wait_loop() {
    auto const timeout = std::chrono::steady_clock::now() + 
        std::chrono::milliseconds(500);
    std::unique_lock<std::mutex> lk(m);
    while (!done) {
        if(cv.wait_until(lk, timeout) == std::cv_status::timeout)
            break;
    }

    return done;
} 

使用消息传递的同步操作

需要确保在应用或者库的实现中,线程不存在共享数据,当然为了线程的通信,消息队列是必须要共享的
每个线程可以进行独立的思考,其行为纯粹基于其所接收到的信息。每个线程就都有一个状态机:当线程接收到一条消息,它将会以某种方式更新其状态,并且可能向其他线程发出一条或多条信息,对于消息的处理依赖于线程的初始化状态

C++内存模型和原子操作

前面讨论了很多C++中的高层工具,下面看一下底层工具是如何让这一切都工作的:C++内存模型和原子操作

C++中的原子操作和原子类型

原子操作是一类不可分割的操作,当这样操作在任意线程中进行一半的时候,是不能查看的;它的状态要不就是完成,要不就是未完成

std::atomic_flag

std::atomic_flag是最简单的标准原子类型,它表示了一个布尔标志。这个类型的对象可以在两个状态间切换:设置和清除
std::atomic_flag类型的对象必须被ATOMIC_FLAG_INIT初始化。初始化的标志位是清除状态。这里没得选择;这个标志总是初始化为清除状态
std::atomic_flag = ATOMIC_FLAG_INIT;
它是唯一需要以如此特殊的方式初始化的原子类型,但它也是唯一保证无锁的类型。标志对象初始化后,只能做三件事:销毁,清除或设置(查询之前的值),这些事情对应的函数分别是:clear()、test_and_set()成员函数

自旋互斥锁

class spinlock_mutex{
    std::atomic_flag flag;
public:
    spinlock_mutex() : flag(ATOMIC_FLAG_INIT) {}

    void lock() {
        while (flag.test_and_set(std::memory_order_acquire));
    }

    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

std::atomic和std::atomic_flag的不同之处在于,std::atomic不是无锁的;为了保证操作的原子性,其实现中需要一个内置的互斥量
特化原子指针std::atomic<T*>

对非原子操作,使用原子操作对操作进行强制排序

基于锁的并发数据结构设计

如何让序列化访问最小化,让真实并发最大化?

线程安全队列

持有std::shared_ptr<>实例的线程安全队列

template<typename T>
class threadsafe_queue{
private:
    mutable std::mutex mut; // 互斥量必须是可变的,empty()
    std::queue<std::shared_ptr<T>> data_queue;
    std::condition_variable data_cond;

public:
    threadsafe_queue(){}

    void push(T new_value) {
        std::shared_ptr<T> data(std::make_shared<T>(std::move(new_value)));

        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(data);
        data_cond.notify_one();
    }

    void wait_and_pop(T& value) {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data_queue.empty();});
        value = std::move(*data_queue.front());
        data_queue.pop();
    }

    std::shared_ptr<T> wait_and_pop() {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data_queue.empty();});
        std::shared_ptr<T> res = data_queue.front();
        data_queue.pop();
        return res;
    }

    bool try_pop(T& value) {
        std::lock_guard<std::mutex> lk(mut);
        if(data_queue.empty()) return false;

        value = std::move(*data_queue.front());
        data_queue.pop();
        return true;
    }

    std::shared_ptr<T> try_pop() {
        std::lock_guard<std::mutex> lk(mut);
        if(data_queue.empty()) return std::shared_ptr<T>();

        std::shared_ptr<T> res = data_queue.front();
        data_queue.pop();
        return res;
    }

    bool empty() const {
        std::lock_guard<std::mutex> lk(mut);
        return data_queue.empty();
    }
};

线程安全的查询表

为细粒度锁设计一个映射结构
例如std::map<>,这里列出三个常见关联容器的方式:

  1. 二叉树,比如:红黑树
  2. 有序数组
  3. 哈希表

为了提高并发访问的概率,选取哈希表作为底层结构
假设有固定数量的桶,每个桶都有一个键值(关键特性),以及散列函数。这就意味着可以安全的对每个桶上锁。当再次使用互斥量(支持多读者单作者)时,就能将并发访问的可能性增加N倍,这里N是桶的数量。当然,对于键值的操作,需要有合适的函数
线程安全的查询表

#include <list>
#include <boost/thread/shared_mutex.hpp>
#include <mutex> // std::unique_lock

template<typename Key, typename Value, typename Hash = std::hash<Key>>
class threadsafe_lookup_table{
private:
    class bucket_type{
    private:
        typedef std::pair<Key, Value> bucket_value;
        typedef std::list<bucket_value> bucket_data;
        typedef typename bucket_data::iterator bucket_iterator;

        bucket_data data;
        mutable boost::shared_mutex mutex;

        bucket_iterator find_entry_for(Key const& key) const {
            return std::find_if(data.begin(), data.end(),
                [&](bucket_value const& item) { return item.first == key;});
        }

    public:
        Value value_for(Key const& key, Value const& default_value) const {
            boost::shared_lock<boost::shared_mutex> lock(mutex);
            bucket_iterator const found_entry = find_entry_for(key);
            return (found_entry == data.end()
                    ? default_value
                    : found_entry->second);
        }

        void add_or_update_mapping(Key const& key, Value const& value) {
            std::unique_lock<boost::shared_mutex> lock(mutex);
            bucket_iterator const found_entry = find_entry_for(key);
            if (found_entry == data.end()) {
                data.push_back(bucket_value(key, value));
            } else {
                found_entry->second = value;
            }
        }

        void remove_mapping(Key const& key) {
            std::unique_lock<boost::shared_mutex> lock(mutex);
            bucket_iterator const found_entry = find_entry_for(key);
            if (found_entry != data.end()) {
                data.erase(found_entry);
            }
        }
    };

    std::vector<std::unique_ptr<bucket_type>> buckets;
    Hash hasher;

    bucket_type& get_bucket(Key const& key) const {
        std::size_t const bucket_index = hasher(key) % buckets.size();
        return *buckets[bucket_index];
    }

public:
    typedef Key key_type;
    typedef Value mapped_type;

    typedef Hash hash_type;
    threadsafe_lookup_table(unsigned num_buckets = 19, Hash const& hasher_ = Hash()) :
            buckets(num_buckets), hasher(hasher_) {
        for (unsigned i = 0; i < num_buckets; ++i) {
            buckets[i].reset(new bucket_type);
        }
    }

    threadsafe_lookup_table(threadsafe_lookup_table const& other) = delete;
    threadsafe_lookup_table& operator=(threadsafe_lookup_table const& other) = delete;

    Value value_for(Key const& key, Value const& default_value = Value()) const {
        return get_bucket(key).value_for(key, default_value);
    }

    void add_or_update_mapping(Key const& key, Value const& value) {
        get_bucket(key).add_or_update_mapping(key, value);
    }

    void remove_mapping(Key const& key) {
        get_bucket(key).remove_mapping(key);
    }
};
# apt-get install libboost-all-dev
# CMakelists.txt 添加boost动态链接库
find_package(Boost)
if (NOT Boost_FOUND)
    message(FATAL_ERROR "could not find boost!")
endif ()
target_link_libraries(demo boost_system)

这个实现中使用了std::vector<std::unique_ptr<bucket_type>>来保存桶,其允许在构造函数中指定构造桶的数量。默认为19个,其是一个任意的质数;哈希表在有质数个桶时,工作效率最高。每一个桶都会被一个boost::shared_mutex实例锁保护,来允许并发读取,或对每一个桶,只有一个线程对其进行修改

无锁并发数据结构设计

使用原子操作的内存序特性,构建无锁数据结构

Linux 终端代理

sudo vi /etc/apt/apt.conf
#vi 下面非插入模式x删除字符
Acquire::http::proxy "http://<proxy>:<port>/";
Acquire::ftp::proxy "ftp://<proxy>:<port>/";
Acquire::https::proxy "https://<proxy>:<port>/";

之后可以安装proxychains对命令进行代理

sudo vi /etc/proxychains.conf
#dpkg -l 可以查询可用包
dpkg -l python*

对象所有权问题

先抛出一个思考的问题,对象构造的时候什么时候选择栈上对象,什么时候选择需要去new一个对象,对象在作为参数传递的时候,谁来保证对象的有效性,所有权如何传递?

对象的构造分为栈上对象和堆上对象。

栈对象

在对栈上的对象也即局部变量对象进行函数参数传递的时候,无非两种选择:值传递或者引用传递。值传递的情况比较简单,会增加数据拷贝的开销,对象越大,付出的代价就会越大,但是不会涉及所有权的问题,在调用者和被调用函数内部对象永远都是有效的,相当于建立了一个对象的副本,同样也不会对原有对象进行更改。因此值传递会带来两个问题:拷贝带来的性能代价和对形参的操作并不会对实参有所改变。

引用传递不会拷贝对象,好像可以避免值传递所带来的问题,但是在某些情况下却会有对象有效性的问题,比如异步回调中局部变量对象的引用

void timer_async_wait(boost::asio::io_service& ios) {
    int a = 1;
    boost::asio::steady_timer timer(ios);
    timer.expires_from_now(std::chrono::seconds(2));
    timer.async_wait([&a](const boost::system::error_code& error){
        std::cout << a << std::endl;
    });
}
void local_ownship_test() {
    boost::asio::io_service ios;
    timer_async_wait(ios);
    ios.run();
}

timer.async_wait的回调闭包中绑定了局部变量a的引用,但是当回调lambda真正执行的时候,栈上的对象a已经被析构了,再对a进行操作属于UB行为。是否需要对引用传递的对象进行有效性判断,个人认为是有必要的,但是怎么判断好像是个死区。因此才有了new一个对象,然后通过智能指针进行管理,如果对象需要共享,则通过shared_ptr<T>weak_ptr<T>去判断被管理对象的有效性

堆对象

堆对象就是通过new产生的对象,堆对象管理方式可以通过原生指针和智能指针,很明显在对堆对象进行函数参数传递的时候,一般都是对管理对象进行传递,也即传递原生指针或智能指针。

堆对象需要共享的话,有效性检查是一个必须要做的工作!如果使用原生指针进行传递好像又是一个无解的行为,比如下面的代码:

void raw_pointer_out(int* a) {
    std::cout << *a << std::endl;
}
void raw_pointer_test() {
    int* a = new int(1);
    delete a;
    raw_pointer_out(a);
}

valgrind运行结果如下:

raw_pointer_test

无效的内存读取,UB行为。那么使用原生指针,在delete指针的时候对指针进行赋值nullptr+判断是否为nullptr来确定对象的有效性可行吗?

看似可行,但是没有考虑下面这种情况:

void raw_pointer_out(int* a) {
    if (a != nullptr) {
        std::cout << *a << std::endl;
    } else {
        std::cout << "nullptr" << std::endl;
    }
}
int* delete_and_set_nullptr(int* a) {
    delete a;
    return nullptr;
}
void raw_pointer_test() {
    int* a = new int(1);

    int* fuck = a;
    fuck = delete_and_set_nullptr(fuck);
    
    raw_pointer_out(a);
}

a不为nullptr但是指向的对象已经被删除了

传递对象的所有权, 开销比复制来得小, 如果可以复制的话.
传递所有权也比”借用”指针或引用来得简单, 毕竟它大大省去了两个用户一起协调对象生命周期的工作.

因此得出的结论是:
1、除了非共享的变量一律创建堆上的对象
2、对象共享的时候使用shared_ptrweak_ptr分别管理对象的所有权和检测对象的有效性,或者使用unique_ptr转移对象的所有权,必须保证在对象调用的时候,被调用对象是活着的

随手记录一些知识点

阻塞

阻塞会block住当前进程,非阻塞通常采用轮询的手段,本质上都是同步的;
只有使用了特殊的 API 才是异步 IO,如Linux的AIO和Windows的IOCP

Docker

Docker 版本号说明

Docker version 1.2.0 (2014-08-20)
docker

container_unixsocket

Docker version 1.11.0 (2016-04-13)

With Docker 1.11, a Linux docker installation is now made of 4 binaries (docker, docker-containerd, docker-containerd-shim and docker-runc)

docker1 11-1

                              +------------+
                              |            |
                              | Docker Hub |
                              |            |
                              +------------+
                                    ↑
                                    |
                                  2 | REST
                                    |
                                    ↓
                               +---------+   unix socket通信
+--------+       REST          |         |   协议是grpc     +-------------------+
| docker |<------------------->| dockerd |<---------------->| docker-containerd |
+--------+         1           |         |      3           +-------------------+
                               +---------+                       ↑
                                                                 |
                                                                 | 4
                                                                 ↓
                                                      +------------------------+  5   +-------------+
                                                      | docker-containerd-shim |<---->| docker-runc |
                                                      +------------------------+      +-------------+
                                                                                             ↑
                                                                                             | 6
                                                                                             ↓
                                                                                         +-------+
                                                                                         | hello |
                                                                                         +-------+

永恒的只有变化,未来的业务都会运行在云上,容器是走向 DevOps、Cloud Native(云原生)的标准工具,已经开始走向平凡,而 Kubernetes 的编排能力,让容器能够落地到业务应用中,所以我们看到 Docker、Mesos、OpenStack 以及很多公有云、私有云服务商,都在支持 Kubernetes,大家都加入了 CNCF(云原生计算基金会)。
简单的说,kubernetes是管理container的工具,openstack是管理VM的工具。

资源可压缩(当缺少资源时,暂停该进程使用不会造成致命错误,不需要终止容器);不可压缩(当缺少资源时,会造成严重影响,需要终止容器)

容器本质上是受到资源限制,彼此间相互隔离的若干个linux进程的集合。一般来说,容器技术主要指代用于资源限制的cgroup,用于隔离的namespace,以及基础的linux kernel等。

Docker的真正核心在于:
它抛弃传统VM试图模拟完整机器的思路,而是以应用为单元进行"集装封箱"

OCI(Open Container Initiative) 开放容器标准 由 docker、coreos 以及其他容器相关公司创建于 2015 年,目前主要有两个标准文档:容器运行时标准 (runtime spec)和 容器镜像标准(image spec)。

006tnc79gy1fl7l7qihpmj30vi0lj756

runc 是 docker 捐赠给 OCI 的一个符合标准的 runtime 实现,目前 docker 引擎内部也是基于 runc 构建的。

docker-spec

X86虚拟化概述

容器化实践的经验分享

各种PaaS弹性云平台,用于DEVOPS,Serverless,任务的弹性调度应对大促。而且基于容器云,还可以做更多的东西,比如大数据,人工智能,区块链,等等。

NAT转换

nat

命名约定

Google开源项目风格指南-命名约定

Linux的文件系统与目录结构解读

win-linux

假设访问上述硬盘第三个分区 dir1 目录中的文件 test.file

Window系统上的路径:E:\dir1\test.file
Linux系统上的路径:/mnt/sda3/dir1/test.file

终于对挂载点有了一点清晰的认识

比起Windows,怎样解读Linux的文件系统与目录结构?

分布式事务一致性与raft&paxos共识

paxos/raft说的是我要在A,B,C上同时做a,保证做完后A,B,C仍然是一致的(至少超过半数达成了共识),一般的实现是在主节点上实施,然后复制到其它节点;分布式事务说的是我要在A机器(或集群)上做a,B机器上做b,C机器上做c,三个操作合在一起还要形成事务,注意这时候没有任何一台机器把a,b,c都运行了一遍来保证事务,而是分布式运行、通过协议协同。

CAP 任何基于网络的数据共享系统
ACID 事务

锁的本质是在存储介质上设置一个标志位,然后通过一些原语操作进行原子性修改

模板实现

编译器会对函数模板进行两次编译。
1、在声明的地方对模板代码本身进行编译;(对其进行词法、语法、句法的分析)
2、在调用的地方对参数替换后的代码进行编译。(生成具体的函数(或类))

du df

du的英文原义为“disk usage”,含义为显示磁盘空间的使用情况。该命令逐级进入指定目录的每一个子目录并显示该目录占用文件系统数据块(1024字节)的情况。若没有给出Names,则对当前目录进行统计。

df命令参数功能:检查文件系统的磁盘空间占用情况,利用所有文件系统对i节点来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。

这也是为什么:

一个被进程打开的文件被rm后,其目录项被删除了,在du命令下是不能被统计到,而其inode没有被删除,在df命令下是可以被统计到的,这就是在du和df存在差别的原因。

异常情况下,df计算的USED空间会比du大很多。

原因在于du是以文件名、目录名为依据计算空间使用的,而df是以硬盘块使用情况来计算空间使用的。

当一个应用程序正在写一个大文件的时候,我们RM或者MV了这个文件(UNIX是允许这么干的,WINDOWS在这一点上傻有傻福),应用程序会占有句柄,并根据句柄所指磁盘位置直接写磁盘,而不会检查该文件是否被删除。

VFS

v2-da5a04d73552cc040c552678178bfc7c_1200x500

vfs

vfs

vfs核心概念:
1、 VFS 通过树状结构来管理文件系统,树状结构的任何一个节点都是“目录节点”
2、 树状结构具有一个“根节点”
3、 VFS 通过“超级块”来了解一个具体文件系统的所有需要的信息。具体文件系统必须先向VFS注册,注册后,VFS就可以获得该文件系统的“超级块”。
4、 具体文件系统可被安装到某个“目录节点”上,安装后,具体文件系统才可以被使用
5、 用户对文件的操作,就是通过VFS 的接口,找到对应文件的“目录节点”,然后调用该“目录节点”对应的操作接口。

inux内核文件系统学习:虚拟文件系统(多图)

process_vfs

epoll select

epoll_select
如果在并发量低,socket都比较活跃的情况下,select就不见得比epoll慢了

进程状态 R S D T Z X

Linux进程状态:R (TASK_RUNNING),可执行状态。
Linux进程状态:S (TASK_INTERRUPTIBLE),可中断的睡眠状态。
Linux进程状态:D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。
Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
Linux进程状态:Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
Linux进程状态:X (TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁。

fork vfork

fork 是 创建一个子进程,并把父进程的内存数据copy到子进程中。
vfork是 创建一个子进程,并和父进程的内存数据share一起用。
clone()可以更细粒度地控制与子进程共享的资源,因而参数也更复杂。

shm mmap

linux中的两种共享内存。一种是我们的IPC通信System V版本的共享内存,另外的一种就是存储映射I/O(mmap函数)
mmap()系统调用使得进程之间可以通过映射一个普通的文件实现共享内存
int shmget(key_t key, size_t size, int shmflg);在物理内存创建一个共享内存,返回共享内存的编号

而对于shm而言,shm每个进程最终会映射到同一块物理内存。shm保存在物理内存,这样读写的速度要比磁盘要快,但是存储量不是特别大。
相对于shm来说,mmap更加简单,调用更加方便,另外mmap有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shmget就会丢失。

Redis

性能 并发
Redis 的过期策略以及内存淘汰机制:定期删除+惰性删除策略。
扫盲,为什么分布式一定要有Redis?

reflect demo

// This file is a "Hello, world!" in C++ language by GCC for wandbox.
#include <iostream>
#include <functional>
#include <string>
#include <cstdlib>
#include <map>

class RunTest {
public:
    struct Args{
        int num1;
        int num2;
    };
    typedef int Reply;
    void add(Args* args, Reply* reply) {
        *reply = args->num1 + args->num2;
    }
};

class Caller {
public:
    typedef std::function<void (void*)> Functor;
    
    template<typename A, typename R>
    struct Wrapper {
        A* args;
        R* reply;
        
        Wrapper(A* a, R* r): args(a), reply(r) {}
    };
    
    template<typename C, typename A, typename R>
    bool reg(std::string method, C* obj, void (C::*(call))(A* args, R* reply));
    
    template<typename A, typename R>
    void call(std::string method, A* args, R* reply);
private:
    std::map<std::string, Functor> reflect;
};

template<typename C, typename A, typename R>
bool Caller::reg(std::string method, C* obj, void (C::*(call))(A* args, R* reply)) {
    
    auto iter = reflect.find(method);
    if (iter != reflect.end()) return false;
    
    reflect[method] = [=](void* v) {
        Wrapper<A, R>* w = (Wrapper<A, R>*)v;
        A* args = w->args;
        R* reply = w->reply;
        
        std::cout << args->num1 << " " << args->num2 << std::endl;
        (obj->*call)(args, reply);
    };
    return true;
}

template<typename A, typename R>
void Caller::call(std::string method, A* args, R* reply) {
    auto iter = reflect.find(method);
    if (iter == reflect.end()) return;
    
    Wrapper<A, R> w{args, reply};
    iter->second(&w);
}

int main()
{
    RunTest rt;
    Caller caller;
    caller.reg("RunTest::add", &rt, &RunTest::add);
    
    RunTest::Args args{1,2};
    RunTest::Reply future;
    caller.call("RunTest::add", &args, &future);
    std::cout << future << std::endl;
    std::cout << "Hello, Wandbox!" << std::endl;
}

// GCC reference:
// https://gcc.gnu.org/

_20180122205902

libev源码分析

libev源码分析 - 从官方例程角度入手

阅读源码之前首先整理了一下手头阅读源码的工具,以visual studio code为主,然后用understand生成了几张调用图辅助源码分析。在分析出大概框架之后可以利用clion配合gdb动态跟进官方的例程进行单步调试验证自己的分析是否正确,进一步理清自己的思路。在分析的时候最好分析出来一部分然后先写出来,一是有助于后面的分析,在后面分析的时候可以结合前面已经写的内容,二是全盘分析结束之后前面有的细节可能会遗忘,如果发现前面分析有误还可以进一步修改

先说一个vs code阅读源码的小技巧,在linux上 Ctrl+ 鼠标左键 可以轻松跳转到代码定义的地方,撤销跳转即返回到跳转前的位置,快捷键为 Ctrl + Alt + -,有了这两个快捷键可以轻松的在阅读源码的时候来回跳转,查看结构体以及函数的原型。

understand生成的UML调用图
UMLClassDiagram

前言

主要分析ev.cev.hev_epoll.c文件,8000行左右的代码量

编译阶段打印宏的内容

//首先定义两个辅助宏
#define   PRINT_MACRO_HELPER(x) #x  // 把参数x转化成字符串
#define   PRINT_MACRO(x) #x "=" PRINT_MACRO_HELPER(x)  // 宏展开
//编译阶段打印宏内容
#pragma message(PRINT_MACRO(EV_API_DECL))

主要记住这几个宏的含义:

// 用于ev_loop指针变量声明
# define EV_P  struct ev_loop *loop               /* a loop as sole parameter in a declaration */
// loop_ptr作为第一个形参,用于函数声明
# define EV_P_ EV_P,                              /* a loop as first of multiple parameters */
// 代指loop
# define EV_A  loop                               /* a loop as sole argument to a function call */
// loop_ptr作为第一个实参,用于函数声明
# define EV_A_ EV_A,                              /* a loop as first of multiple arguments */

Reactor模式

Reactor_Structures

官方例程

// a single header file is required
#include <ev.h>
#include <stdio.h> // for puts
// every watcher type has its own typedef'd struct
// with the name ev_TYPE
ev_io stdin_watcher;  // IO事件
ev_timer timeout_watcher; // 定时器事件
// all watcher callbacks have a similar signature
// this callback is called when data is readable on stdin
static void
stdin_cb (EV_P_ ev_io *w, int revents)
{
    puts ("stdin ready");
    // for one-shot events, one must manually stop the watcher
    // with its corresponding stop function.
    ev_io_stop (EV_A_ w);
    // this causes all nested ev_run's to stop iterating
    ev_break (EV_A_ EVBREAK_ALL);
}
// another callback, this time for a time-out
static void
timeout_cb (EV_P_ ev_timer *w, int revents)
{
    puts ("timeout");
    // this causes the innermost ev_run to stop iterating
    ev_break (EV_A_ EVBREAK_ONE);
}

int
main (void)
{
    // use the default event loop unless you have special needs
    struct ev_loop *loop = EV_DEFAULT;
    // initialise an io watcher, then start it
    // this one will watch for stdin to become readable
    ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);  // 设置对stdin_watcher这个fd关注读事件,并指定回调函数
    ev_io_start (loop, &stdin_watcher);  // // 激活stdin_watcher这个fd,将其设置到loop中
    // initialise a timer watcher, then start it
    // simple non-repeating 5.5 second timeout
    ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.); //设置一个定时器,并指定一个回调函数,这个timer只执行一次,5.5s后执行
    ev_timer_start (loop, &timeout_watcher); //激活这个定时器,将其设置到loop中

    // now wait for events to arrive
    ev_run (loop, 0);
    // break was called, so exit
    return 0;
}

ev_loop

# define EV_DEFAULT ev_default_loop (0) /* the default loop as sole arg */

#if EV_MULTIPLICITY
struct ev_loop * ecb_cold
#else
int
#endif
ev_default_loop (unsigned int flags) EV_THROW
{
  if (!ev_default_loop_ptr)
    {
#if EV_MULTIPLICITY
      EV_P = ev_default_loop_ptr = &default_loop_struct;
#else
      ev_default_loop_ptr = 1;
#endif

      loop_init (EV_A_ flags);

      if (ev_backend (EV_A))
        {
#if EV_CHILD_ENABLE
          ev_signal_init (&childev, childcb, SIGCHLD);
          ev_set_priority (&childev, EV_MAXPRI);
          ev_signal_start (EV_A_ &childev);
          ev_unref (EV_A); /* child watcher should not keep loop alive */
#endif
        }
      else
        ev_default_loop_ptr = 0;
    }

  return ev_default_loop_ptr;
}

看这段代码的时候一直在疑惑ev_default_loop_ptr是从哪里来的,怎么变量没有声明就将default_loop_struct地址赋值过去了。。。

玄机都隐藏在下面的代码中:

EV_API_DECL struct ev_loop *ev_default_loop_ptr = 0; /* needs to be initialised to make it a definition despite extern */

注意:既指定的了关键字extern又指定了一个显示的初始值的全局对象的声明,将被视为该对象的定义!

即这里是定义!定义了一个ev_loop的指针并初始化为0

因此ev_default_loop_ptr是一个在ev.c文件中定义的全局变量!

ev_default_loop(0)主要的工作是:

  1. 将全局对象ev_default_loop_ptr即ev_loop的指针初始化为默认loopstatic struct ev_loop default_loop_struct;的地址,初始化ev_loop结构体字段
  2. 在linux下将backend即同步事件分离器初始化为epoll,IO多路复用。可以同时监听多个文件句柄,从而提高系统的并发性,但是epoll本身是阻塞的
#if EV_USE_EPOLL
      if (!backend && (flags & EVBACKEND_EPOLL )) backend = epoll_init  (EV_A_ flags);
#endif

ev_io

ev_watcher结构体

2017-07-12_09-58-57

ev_watcher_list

#define EV_WATCHER_LIST(type)			\
  EV_WATCHER (type)				\
  struct ev_watcher_list *next; /* private */

ev_io

/* invoked when fd is either EV_READable or EV_WRITEable */
/* revent EV_READ, EV_WRITE */
typedef struct ev_io
{
  EV_WATCHER_LIST (ev_io)

  int fd;     /* ro */
  int events; /* ro */
} ev_io;

ev_io_init

#define ev_io_init(ev,cb,fd,events)          do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)

/* these may evaluate ev multiple times, and the other arguments at most once */
/* either use ev_init + ev_TYPE_set, or the ev_TYPE_init macro, below, to first initialise a watcher */
#define ev_init(ev,cb_) do {			\
  ((ev_watcher *)(void *)(ev))->active  =	\
  ((ev_watcher *)(void *)(ev))->pending = 0;	\
  ev_set_priority ((ev), 0);			\
  ev_set_cb ((ev), cb_);			\
} while (0)

#define ev_io_set(ev,fd_,events_)            do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)

使用do{}while(0)可以很好的包裹宏展开

初始化的时候很好的利用了ev_io中ev_watcher_list在结构体顶部,ev_watcher在ev_watcher_list顶部,有一个指针的向上强转

  ((ev_watcher *)(void *)(ev))->active  =	\
  ((ev_watcher *)(void *)(ev))->pending = 0;	\

事件默认优先级为0

ev_io_start

void noinline
ev_io_start (EV_P_ ev_io *w) EV_THROW
{
  int fd = w->fd;

  if (expect_false (ev_is_active (w)))
    return;

  assert (("libev: ev_io_start called with negative fd", fd >= 0));
  assert (("libev: ev_io_start called with illegal event mask", !(w->events & ~(EV__IOFDSET | EV_READ | EV_WRITE))));

  EV_FREQUENT_CHECK;

  ev_start (EV_A_ (W)w, 1); // [1]
  array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);
  wlist_add (&anfds[fd].head, (WL)w); // [2]

  /* common bug, apparently */
  assert (("libev: ev_io_start called with corrupted watcher", ((WL)w)->next != (WL)w));

  fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);
  w->events &= ~EV__IOFDSET;

  EV_FREQUENT_CHECK;
}

expect_false中用到了GCC内建函数__builtin_expect()

#define ecb_expect_false(expr) ecb_expect (!!(expr), 0)

由于大部分程序员在分支预测方面做得很糟糕,所以 GCC 提供了这个内建函数来帮助程序员处理分支预测,优化程序。其第一个参数 exp 为一个整型表达式,这个内建函数的返回值也是这个 exp ,而 c 为一个编译期常量。这个函数的语义是:你期望 exp 表达式的值等于常量 c ,从而 GCC 为你优化程序,将符合这个条件的分支放在合适的地方。

现在处理器都是流水线的,有些里面有多个逻辑运算单元,系统可以提前取多条指令进行并行处理,但遇到跳转时,则需要重新取指令,这相对于不用重新去指令就降低了速度。所以就引入了 __builtin_expect,目的是增加条件分支预测的准确性,cpu 会提前装载后面的指令,遇到条件转移指令时会提前预测并装载某个分 支的指令。确认该条件是极少发生的,还是多数情况下会发生。编译器会产生相应的代码来优化 cpu 执行效率。

  1. 调整ev_watcher的优先级,设置active为1,将loop的activecnt++递增,即当前loopptr上挂了多少个激活的ev_watcher
  2. 将ev_watcher_list挂载到loop.anfds.head上

ev_run

整个loop事件循环的主体

do{
	xxxx;
	backend_poll(); // [1]
	EV_INVOKE_PENDING // [2]
}while(condition_is_ok)
  1. fd上的watcher如果监听的事件event和epoll得到的revent一致,则将该watcher添加到loopptr->pendings[pri][pendingcnt]上,loopptr->pendings是一个指针数组,相当于在第二维上动态的二维数组
  2. ev_invoke_pending对应调度器dipatcher,从后到前依次调用挂在pendings上的回调

相关数据结构如下:

215034_LAfF_917596

总结

libev整个事件模型的框架是: 取得一个合适的时间,用这个时间去poll。然后标记poll之后pending的文件对象。poll出来后判断定时器然后统一处理pending对象

在阅读源码之后写了一个小轮子event-driven-framework地址如下:
event-driven-framework :
https://github.com/v4if/event-driven-framework

关于anfds

libev在关联fd和watcher的时候利用了fd作为下标,然后挂了watcher_list

从alloc_fd的实现上看,一般情况下,Linux每次都从上一次分配的fd(利用文件表中的一个变量next_fd记录),来开始查找未用的文件描述符。这样保证新分配的文件描述符都是持续增长的,直到上限,然后回绕。
close的内核实现,它调用__put_unused_fd用于释放文件描述符。

static void __put_unused_fd(struct files_struct *files, unsigned int fd)
{
  struct fdtable *fdt = files_fdtable(files);
  __FD_CLR(fd, fdt->open_fds);
  if (fd < files->next_fd)
  files->next_fd = fd;
}

从上面的代码中,可以发现,当释放的fd比文件表中的nextfd小的话,nextfd就会更新为当前fd。
结合alloc_fd的代码,进一步得到Linux文件描述符的选择策略。当持有的文件描述符关闭时,Linux会尽快的重用该文件描述符,而不是使用递增的文件描述符值

因此在anfds上可能会存在空洞,也存在文件描述符重用之后没有更新watcher的隐患

同步异步、阻塞非阻塞

同步和异步
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发I/O操作并等待或者轮询的去查看I/O操作是否就绪,而异步是指用户进程触发I/O操作以后便开始做自己的事情,而当I/O操作已经完成的时候会得到I/O完成的通知。

阻塞和非阻塞
阻塞和非阻塞是针对于进程在访问数据的时候,根据I/O操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。

I/O模型

同步阻塞I/O

在此种方式下,用户进程在发起一个I/O操作以后,必须等待I/O操作的完成,只有当真正完成了I/O操作以后,用户进程才能运行。Java传统的I/O模型属于此种方式。

同步非阻塞I/O

在此种方式下,用户进程发起一个I/O操作以后边可返回做其它事情,但是用户进程需要时不时的询问I/O操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。目前Java的NIO就属于同步非阻塞I/O。

异步阻塞I/O

此种方式下是指应用发起一个I/O操作以后,不等待内核I/O操作的完成,等内核完成I/O操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问I/O是否完成,那么为什么说是阻塞的呢?因为此时是通过 select 系统调用来完成的,而 select 函数本身的实现方式是阻塞的,而采用 select 函数有个好处就是它可以同时监听多个文件句柄,从而提高系统的并发性。

异步非阻塞I/O

在此种模式下,用户进程只需要发起一个I/O操作然后立即返回,等I/O操作真正的完成以后,应用程序会得到I/O操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的I/O读写操作,因为真正的I/O读取或者写入操作已经由内核完成了。目前Java中还没有支持此种I/O模型。

使用gdb探究C++内存布局

gdb使用技巧

每行打印一个结构体成员

可以执行set print pretty on命令,这样每行只会显示结构体的一名成员,而且还会根据成员的定义层次进行缩进

按照派生类打印对象

set print object on

set p obj <on/off>: 在C++中,如果一个对象指针指向其派生类,如果打开这个选项,GDB会自动按照虚方法调用的规则显示输出,如果关闭这个选项的话,GDB就不管虚函数表了。这个选项默认是off。 使用show print object查看对象选项的设置。

查看虚函数表

在 GDB 中还可以直接查看虚函数表,通过如下设置:set print vtbl on

之后执行如下命令查看虚函数表:info vtbl 对象或者info vtbl 指针或引用所指向或绑定的对象

c++filt

GNU提供的从name mangling后的名字来找原函数的方法,如c++filt _ZTV1A,在命令行下运行

打印内存的值

gdb中使用“x”命令来打印内存的值,格式为“x/nfu addr”。含义为以f格式打印从addr开始的n个长度单元为u的内存值。参数具体含义如下:

a)n:输出单元的个数。

b)f:是输出格式。比如x是以16进制形式输出,o是以8进制形式输出,a 表示将值当成一个地址打印,i 表示将值当作一条指令打印,等等。

c)u:标明一个单元的长度。b是一个byte,h是两个byte(halfword),w是四个byte(word),g是八个byte(giant word)。

打印表达式的值

p 命令可以用来打印一个表达式的值。

使用如下:p/f 表达式

f 代表格式控制符,同上。

单继承

#include <iostream>
class A{
public:
    int a;
    A():a(0x1) {}
    virtual void foo(){ std::cout << "A::foo()" << std::endl; }
    void bar(){ std::cout << "A::bar()" << std::endl; }
};
class B: public A{
public:
    int b;
    B():A(),b(0x2) {}
    void foo(){ std::cout << "B::foo()" << std::endl; }
};
class C: public B{
public:
    int c;
    C():C(), c(0x3) {}
    void foo(){ std::cout << "C::foo()" << std::endl; }
};
int main() {
    A a; B b; C c; B *p = &c;
    p->foo();
    std::cout << sizeof(int) << " " << sizeof(int*) << std::endl;
    return 0;
}

运行结果

C::foo()
4 4

int和int* 都是4个字节,且p为基类B的指针,指向派生类C,virtual foo()函数运行时多态

对象内存布局

(gdb) set p pre on
(gdb) p a
$1 = (A) {
  _vptr.A = 0x405188 <vtable for A+8>, 
  a = 1
}

(gdb) p/a &a 
$2 = 0x28ff24
(gdb) p/a &a.a  
$3 = 0x28ff28
(gdb) p sizeof(a)
$4 = 8
(gdb) x/2xw &a
0x28ff24:	0x00405188	0x00000001

(gdb) set p vtbl on
(gdb) info vtbl a
vtable for 'A' @ 0x405188 (subobject @ 0x28ff24):
[0]: 0x403bf8 <A::foo()>
  • _vptr.A 代表a对象所含有的虚函数表指针,0x405188为第一个虚函数也即foo()的地址,真正虚函数表的起始地址为0x405188 - 8,还会有一些虚函数表头信息
    vptr 总是指向 虚函数表的第一个函数入口
  • 对象a所在的地址为0x28ff24,整个对象占8个字节,其中4个字节为vptr虚函数表指针,4个字节为数据int a
  • 虚函数表 vtable for 'A' @0x405188
(gdb) p b
$5 = (B) {
  <A> = {
    _vptr.A = 0x405194 <vtable for B+8>, 
    a = 1
  }, 
  members of B: 
  b = 2
}
(gdb) p sizeof(b)
$6 = 12
(gdb) p c
$7 = (C) {
  <B> = {
    <A> = {
      _vptr.A = 0x4051a0 <vtable for C+8>, 
      a = 1
    }, 
    members of B: 
    b = 2
  }, 
  members of C: 
  c = 3
}
(gdb) p sizeof(c)
$8 = 16
  • 如果class B中申明了新的虚函数(比如foo2),class B中依然只有一个虚函数表,只不过会把foo2加入到该表中。此时class A的虚函数表不会包含foo2。

多重继承

class A{
    int a;
    virtual void foo(){ std::cout << "A::foo()" << std::endl; }
};
class B{
    int b;
    virtual void bar(){ std::cout << "B::bar()" << std::endl; }
};
class C: public A, public B{
    int c;
    void foo(){ std::cout << "C::foo()" << std::endl; }
    void bar(){ std::cout << "C::bar()" << std::endl; }
};

对象内存布局

(gdb) set p pre on
(gdb) p a
$1 = (A) {
  _vptr.A = 0x4051a0 <vtable for A+8>, 
  a = 4201067
}
(gdb) p b
$2 = (B) {
  _vptr.B = 0x4051ac <vtable for B+8>, 
  b = 4200976
}
(gdb) p/a c 
$3 = (C) {
  <A> = {
    _vptr.A = 0x4051b8 <vtable for C+8>, 
    a = 0x75e78cd5 <msvcrt!atan2+431>
  }, 
  <B> = {
    _vptr.B = 0x4051c8 <vtable for C+24>, 
    b = 0xfffffffe
  }, 
  members of C: 
  c = 0x75e61162 <onexit+53>
}
(gdb) p sizeof(c)
$4 = 20
(gdb) x/5aw &c
0x28ff0c:	0x4051b8 <_ZTV1C+8>	0x75e78cd5 <msvcrt!atan2+431>	0x4051c8 <_ZTV1C+24>	0xfffffffe
0x28ff1c:	0x75e61162 <onexit+53>
  • 数据成员int a, int b, int c都未初始化,此时是UB未定义行为
  • 对象c含有两个虚函数表指针_vptr.A和_vptr.B,占用20个字节内存,3个int数据成员,两个虚函数表指针
  • 对象c的内存布局为 c: vptr.A | a | vptr.B | b | c

虚继承

class A{
    ...  
    virtual void foo(){ cout << "A::foo()" << endl; }
};
class B: virtual public A{
    ...
    virtual void foo(){ cout << "B::foo()" << endl; }
};
class C: virtual public A{
    ...
    virtual void foo(){ cout << "C::foo()" << endl; }
};
class D: public B, public C{
    ...
    virtual void foo(){ cout << "D::foo()" << endl; }
};

对象内存布局

(gdb) set p pre on
(gdb) p a
$1 = (A) {
  _vptr.A = 0x405238 <vtable for A+8>, 
  a = 4201067
}
(gdb) p b
$2 = (B) {
  <A> = {
    _vptr.A = 0x405258 <vtable for B+28>, 
    a = 4200976
  }, 
  members of B: 
  _vptr.B = 0x405248 <vtable for B+12>, 
  b = 1978012002
}
(gdb) p c
$3 = (C) {
  <A> = {
    _vptr.A = 0x405278 <vtable for C+28>, 
    a = -2079145649
  }, 
  members of C: 
  _vptr.C = 0x405268 <vtable for C+12>, 
  c = 2686916
}
(gdb) p d
$4 = (D) {
  <B> = {
    <A> = {
      _vptr.A = 0x4052a8 <vtable for D+44>, 
      a = 2686704
    }, 
    members of B: 
    _vptr.B = 0x405288 <vtable for D+12>, 
    b = -237228229
  }, 
  <C> = {
    members of C: 
    _vptr.C = 0x405298 <vtable for D+28>, 
    c = 0
  }, 
  members of D: 
  d = 0
}
(gdb) p &d
$5 = (D *) 0x28fee8
  • 对象内存布局
    a: vptr.A | a
    b: vptr.A | a | vptr.B | b
    c: vptr.A | a | vptr.C | c
    d: vptr.A | a | vptr.B | b | vptr.C | c | d
  • A *pa = &d;B *pb = &d;C*p c= &d;,都指向d的起始地址&d = 0x28fee8 。假如d类里实现的虚函数都放在A的虚函数表里,没有实现的放在被继承的基类里面

虚函数表里到底有些什么

/* vtable.cpp */
class A{
public:
    int ia;
    virtual void foo(){ cout << "A::foo()" << endl; }
    virtual void bar(){ cout << "A::bar()" << endl; }
};
class B: public A{
public:
    int ib;
    virtual void foo(){ cout << "B::foo()" << endl; }
};
我们可以利用g++ -fdump-class-hierarchy vtable.cpp得到class Aclass B的vtable(结果在文件vtable.cpp.002t.class里),如下所示:

Vtable for A
A::_ZTV1A: 4u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI1A)
8     (int (*)(...))A::foo
12    (int (*)(...))A::bar

参考资料

C++内存模型 (一)多态与继承
图说C++对象模型:对象内存布局详解

常见问题 Q&A

'libproxychains.so.3' from LD_PRELOAD cannot be preloaded

➜  ~  find /usr/ -name libproxychains.so.3 -print
/usr/lib/x86_64-linux-gnu/libproxychains.so.3
➜  ~  export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libproxychains.so.3

cygwin apt-cyg

You can do this using Cygwin’s setup.exe from Windows command line. Example:

cd C:\cygwin64
setup-x86_64 -q -P wget,tar,qawk,bzip2,subversion,vim

For a more convenient installer, you may want to use the apt-cyg package manager. Its syntax similar to apt-get, which is a plus. For this, follow the above steps and then use Cygwin Bash for the following steps:

wget rawgit.com/transcode-open/apt-cyg/master/apt-cyg
install apt-cyg /bin

Now that apt-cyg is installed. Here are few examples of installing some packages:

apt-cyg install nano
apt-cyg install git
apt-cyg install ca-certificates

cygwin 代理

# vim ~/.bashrc
export http_proxy=http://YouProxyIP:port
export https_proxy=http://YouProxyIP:port

cygwin 中文显示问题

win的Linux子系统下面不支持netstat命令,可以在cygwin下面查看
在Cygwin终端上右键-->Options…-->Text-->修改Locale 为 zh_CN,Character Set 为 GBK,问题便得到解决。

kali 数字签名过期

Invalid signature for Kali Linux repositories : “The following signatures were invalid: EXPKEYSIG ED444FF07D8D0BF6 Kali Linux Repository”

请注意,如果您在一段时间内未更新Kali安装(tsk2),您将希望收到有关存储库密钥过期的GPG错误(ED444FF07D8D0BF6)。幸运的是,通过以root身份运行以下内容可以快速解决此问题:

wget -q -O - https://archive.kali.org/archive-key.asc | apt-key add

How to know the version of installed package?

apt-cache policy <package name>
The above command will shows installed package version and also all the available versions in the repository according to the version of Ubuntu in which you are running.It doesn't display the package version which was intended for another version of Ubuntu(not your's).

Example:
$ apt-cache policy gparted
gparted:
  Installed: 0.16.1-1
  Candidate: 0.16.1-1
  Version table:
 *** 0.16.1-1 0
        500 http://ubuntu.inode.at/ubuntu/ saucy/main amd64 Packages
        100 /var/lib/dpkg/status
So the installed gparted version is 0.16.1-1.

How to install a specific package version?
sudo apt-get install package=version
Example:
$ sudo apt-get install gparted=0.16.1-1
Reading package lists... Done
Building dependency tree       
Reading state information... Done
gparted is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 265 not upgraded.

右值引用:移动语义与完美转发

左值、右值

可以取地址的、有名字的就是左值,反之就是右值

在C++11中,右值由两个概念构成,一个是将亡值,另一个则是纯右值。纯右值是C++98标准中右值的概念,讲的是用于辨识临时变量和一些不跟对象关联的值。比如非引用返回的函数返回的临时变量值就是一个纯右值。一些运算表达式,比如1+3产生的临时变量值。而不跟对象关联的字面量值,比如:2、'c'、true也是纯右值。此外,类型转换函数的返回值、lambda表达式等也都是纯右值;而将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值、转换为T&&的类型转换函数的返回值。而剩余的,可以标识函数、对象的值都属于左值。

在C++11的程序中,所有的值必属于左值、将亡值、纯右值三者之一。

在C++11中,右值引用就是对一个右值进行引用的类型。事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在

移动语义

// 关闭编译器返回值优化
// set(CMAKE_CXX_FLAGS -fno-elide-constructors)
#include <iostream>

struct HasPtrMem{
    HasPtrMem(): ptr(new int(0)) {
        std::cout << "construct:" << ++n_constructor <<  std::endl;
    }
    HasPtrMem(const HasPtrMem& rhs): ptr(new int(*rhs.ptr) {
        std::cout << "copy construct:" << ++n_copy_constructor << std::endl;
    }
    ~HasPtrMem() {
        std::cout << "destruct:" << ++n_destructor << std::endl;
        delete(ptr);
    }

    int* ptr;
    static int n_constructor;
    static int n_destructor;
    static int n_copy_constructor;
};

int HasPtrMem::n_constructor = 0;
int HasPtrMem::n_destructor = 0;
int HasPtrMem::n_copy_constructor = 0;

HasPtrMem getTemp() {
    return HasPtrMem();
}

int main() {
    HasPtrMem a = getTemp();
    return 0;
}

/*
construct:1
copy construct:1
destruct:1
copy construct:2
destruct:2
 */

这里构造函数被调用了一次,这是在GetTemp函数中HasPtrMem()表达式显示的调用了构造函数而打印出来的。而拷贝构造函数调用了两次。这两次一次是从GetTemp函数中HasPtrMem()生成的变量上拷贝构造出一个临时值,以用作GetTemp的返回值,而另外一次则是有临时值构造出main中变量a调用的。对应地,析构函数也就调用了3次。

2017-08-07_21-34-34

整个过程中如果HasPtrMem的指针指向非常大的堆内存的话,那么拷贝构造的过程就会非常昂贵。而且临时变量的产生和销毁以及拷贝的发生对于程序员来说基本上是透明的,不会影响程序的正确性,因而即使该问题导致程序的性能不如预期,也不易被察觉(事实上,编译器常常对函数返回值有专门的优化,上述代码编译的时候需要把返回值优化关掉)

为了转移对象资源的所有权问题,就出现了移动语义,从临时变量中拷贝构造变量a时,使得a.d指向临时对象的堆内存资源。同时保证临时对象不释放所指向的堆内存,那么在构造完成后,临时对象被析构,a就从中"偷"到了临时对象所拥有的堆内存资源。

#include <iostream>

struct HasPtrMem{
    HasPtrMem(size_t _size = 0): ptr(new int(_size > 0 ? _size : 1)) {
        std::cout << "construct:" << ++n_constructor <<  std::endl;
    }
    HasPtrMem(const HasPtrMem& rhs) {
        std::cout << "copy construct:" << ++n_copy_constructor << std::endl;
    }
    HasPtrMem(HasPtrMem&& rhs): ptr(rhs.ptr) {
        rhs.ptr = nullptr;
        std::cout << "move construct:" << ++n_move_constructor << std::endl;
    }
    ~HasPtrMem() {
        std::cout << "destruct:" << ++n_destructor << std::endl;
        delete ptr;
    }

    int* ptr;
    static int n_constructor;
    static int n_destructor;
    static int n_copy_constructor;
    static int n_move_constructor;
};

int HasPtrMem::n_constructor = 0;
int HasPtrMem::n_destructor = 0;
int HasPtrMem::n_copy_constructor = 0;
int HasPtrMem::n_move_constructor = 0;

HasPtrMem getTemp() {
    HasPtrMem h;
    std::cout << "Resource from " << __func__ << ":" << std::hex << h.ptr << std::endl;
    return h;
}

int main() {
    HasPtrMem a = getTemp();
    std::cout << "Resource from " << __func__ << ":" << std::hex << a.ptr << std::endl;
    return 0;
}
/*
construct:1
Resource from getTemp:0x55ca87d79c20
move construct:1
destruct:1
move construct:2
destruct:2
Resource from main:0x55ca87d79c20
destruct:3
 */

这里没有调用拷贝构造函数,而是调用了两次移动构造函数,GetTemp中的h的指针成员h.ptr和main中的a的指针成员a.ptr的值是相同的,即都指向了相同的堆地址内存。该堆内存在函数返回的过程中,成功逃避了析构。

移动语义何时会被触发,一旦我们用到的是个临时变量,那么移动语义就可以得到执行。

只要能够绑定右值的引用类型,都能够延长右值的生命期

2017-08-07_22-23-05

在C++11中可以使用std::move强制转化为右值,触发移动语义,不过需要转化为右值引用的应是一个确实生命期即将结束的对象,被转化为右值之后,该对象的资源指向nullptr不可再被使用。

为了保证移动语义的传递,在编写移动构造函数的时候,应该总是记得使用std::move转换拥有形如堆内存、文件句柄等资源的成员为右值,这样以来,如果成员支持移动构造的话,就可以实现其移动语义。而即使没有移动构造函数,那么接受常量左值的构造函数版本也会实现拷贝构造。

完美转发

在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数,比如:

template<typename T>
void FunForwording(T t){RunCode(t);}

这里在FunForwording的参数中使用了最基本的类型进行转发,因此在实参传给FunForwording之前就会产生一次额外的临时对象拷贝。在不产生额外的开销,就好像转发者不存在一样,则称为完美转发,完美转发的一个作用就是做包装函数。

所以通常需要的是引用类型,引用类型不会产生拷贝的额外的开销。其次需要考虑转发函数对类型的接受能力,因为目标函数可能需要能够既接受左值引用,又接受右值引用。如果使用常量左值引用const T& t作为参数,虽然转发函数接受能力提高了,但在目标函数的接受上却出了问题,如果目标函数的参数是非常量左值引用类型,则不能接受转发函数的参数。

C++11引入了一条所谓引用折叠的新语言规则

typedef const int T;
typedef T& TR;
TR& v = 1; // 左值引用

2017-08-07_22-44-16

一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用

因此可以把转发函数携程如下形式:

template<typename T>
void FunForwording(T&& t){
  std::cout << std::is_rvalue_reference<T&&>::value << std::endl;
  RunCode(static_cast<T&&>(t));
}

关于完美转发之前有一个疑问,T&& t只能接受右值引用?转发函数接受能力受限?其实不然。

C++11中,T&&这种语法形式有两种意思:

  • 右值引用(Rvalue reference),只能绑定右值
  • 万能引用(Universal reference),既能绑定右值,又能绑定左值。只有发生类型推导的时候,T&&才表示万能引用。
template<typename T>
void Foo(T && arg);  // 发生类型推导,T&&表示万能引用
auto && i = 42;      // 发生类型推导,T&&表示万能引用

void Foo(int && arg);  // 没有类型推导,int&&表示右值引用,只能绑定右值
int && rRef = 3;       // 没有类型推导,int&&表示右值引用,只能绑定右值

static_cast是留给右值引用的,可以接受右值的右值引用本身却是个左值,因此就必须使用std::move来进行左右值的转换。而std::move通常就是一个static_cast。不过在C++11中,用于完美转发的函数却不再叫做move,而是另外一个名字:forward。

template<typename T>
void FunForwording(T&& t){RunCode(forward<T>(t));}

参考资料

《深入理解C++11-新特性解析与应用》

C++ 变长模板

变长函数

C++11已经支持了C99的变长宏

#include <stdio.h>
#include <stdarg.h>

double sum_of_float(int argc, ...) {
  va_list ap;
  double sum = 0;
  va_start(ap, argc); //获得变长参数句柄 argv
  for (int i = 0; i < argc; i++) {
    sum += va_arg(ap, double); //每次获得一个参数
  }
  va_end(argv);
  
  return sum;
}

变长函数的第一个参数argc表示的是变长参数的个数,这必须由sum_of_float的调用者传进来。而在函数中需要通过一个类型为va_list的数据结构argv来辅助地获得参数

20170811222450

整个机制设计上变长函数本身完全无法知道参数数量或者参数类型

C++引入了一种变长参数的实现方式,即类型和变量同时能够传递给变长参数的函数,一个好的方式就是使用C++的模板

变长模板

template<typename... Elements>class tuple;

在标识符elements之前使用了省略号来表示该参数是变长的。在C++11中被称作是一个模板参数包

一个模板参数包在模板推导时会被认为是模板的单个参数(虽然实际上它将会打包任意数量的实参)。为了使用模板参数包,总是需要将其解包。在C++11中,通常是通过一个名为包扩展的表达式来完成

template<typename... Elements> class tuple; //变长模板的声明

template<typename Head, typename... Tail> //递归的偏特化定义
class tuple<Head, Tail...> : private tuple<Tail...> {
 Head head;
};

template<> class tuple<> {}; //边界条件

用变长模板实现tuple的一种方式,这个思路是使用数学的归纳法,转换为计算机能够实现的手段则是递归。通过定义递归的模板偏特化定义,可以使得模板参数包在实例化时能够层层展开,直到参数包中的参数逐渐耗尽或到达某个数量的边界为止

在C++11中,标准定义了7中参数包可以展开的位置:

  • 表达式
  • 初始化列表
  • 基类描述列表
  • 类成员初始化列表
  • 模板参数列表
  • 通用属性列表
  • lambda函数的捕捉列表

语言的其他地方则无法展开参数包

#include <iostream>
#include <typeinfo>

template<typename... T>
void dummy_wrapper(T...) {}

template<typename Element>
Element v_print(Element e) {
    std::cout << e << std::endl;
    return e;
}

template<typename... Elements>
void func(Elements... E) {
    dummy_wrapper(v_print(E)...);
}

int main() {
    func<int, double, int>(1, 1.2f, 5);
    return 0;
}

参考资料

《深入理解C++11-新特性解析与应用》

网络发包格式问题

RAW Socket

假如用raw socket构造原生套接字,对端ip为192.168.0.100,ip表示为点分10进制,因此需要先转换为16进制
192.168.0.100 -> c0a80064
如果直接使用raw_socket.send("c0a80064")得到的包为63 30 61 38 30 30 36 34,即会当做ascii处理,然后发送对应的hex值
这当然不是想要的结果,想要发送的结果是原生hex值,即\xc0\xa8\x00\x64,4个hex字节表示对端ip

方式一:使用struct.pack到二进制发包

# coding:utf-8
import socket  
import struct  

raw_socket = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.IPPROTO_RAW)
raw_socket.bind(("eth0", socket.htons(0x0800)))

#0x0800 ipv4 type
packet = struct.pack("!6s6s2s",'\xaa\xaa\xaa\xaa\xaa\xaa','\xbb\xbb\xbb\xbb\xbb\xbb','\x08\x00') 

raw_socket.send(packet + "Hello, world!") 

方式二: 先将要发送的内容转成字符串(16进制),然后利用decode或者binascii.a2b_hex将字符串转为binary hex

tim 20180107231507

ip_dot_str= "192.168.0.100"
arr = [hex(int(x, 10))[2:].zfill(2) for x in ip_dot_str.split('.')]
hex_binary = ''.join(arr).decode('hex')

JavaScript类型判断

JavaScript作为一种弱类型语言,类型判断如同鸡肋一样

>  undefined == null
<- true

what the fuck?
一种语言,两个表示无的值.

null表示"没有对象",即该处不应该有值.
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义

接下来看一下typeof操作符,typeof基本只有两个用途,就是检测一个元素是否为undefined,或者是否为function,如果作为类型判断就几乎不可能得到想要的结果.

Value               value   typeof
-------------------------------------
"foo"               String     string
new String("foo")   String     object
1.2                 Number     number
new Number(1.2)     Number     object
true                Boolean    boolean
new Boolean(true)   Boolean    object
new Date()          Date       object
new Error()         Error      object
[1,2,3]             Array      object
new Array(1, 2, 3)  Array      object
new Function("")    Function   function
/abc/g              RegExp     object
new RegExp("meow")  RegExp     object
{}                  Object     object
new Object()        Object     object 

那么instanceof操作符是否可行,instanceof是比较两个操作数的构造函数,在比较内置类型时作用不大

// 比较自定义对象
function Foo() {}
function Bar() {}
Bar.prototype = new Foo();

new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true

// 比较内置类型
new String('foo') instanceof String; // true
new String('foo') instanceof Object; // true

'foo' instanceof String; // false
'foo' instanceof Object; // false

最终方案:Object.prototype.toString(),jQuery.type()实现方式:

var class2type = {} ;
"Boolean Number String Function Array Date RegExp Object Error".split(" ").forEach(function(e,i){
    class2type[ "[object " + e + "]" ] = e.toLowerCase();
}) ;
//当然为了兼容IE低版本,forEach需要一个polyfill,不作细谈了。
function _typeof(obj){
    if ( obj == null ){
        return String( obj );
    }
    return typeof obj === "object" || typeof obj === "function" ?
    class2type[ Object.prototype.toString.call(obj) ] || "object" :
        typeof obj;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.